diff --git a/.github/workflows/coverage_check.yml b/.github/workflows/coverage_check.yml new file mode 100644 index 0000000..5185fcd --- /dev/null +++ b/.github/workflows/coverage_check.yml @@ -0,0 +1,70 @@ +name: Test and Coverage Check + +on: + push: + branches: + - main + - development + +jobs: + test-and-check-coverage: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Set up Go + uses: actions/setup-go@v2 + with: + go-version: '1.21.7' + + - name: Install dependencies and update go.mod/go.sum + run: | + go get -t -v ./... + go mod tidy + + - name: Run tests + run: | + go test -cover ./usecases/admin/... + go test -cover ./usecases/category/... + go test -cover ./usecases/chatbot/... + go test -cover ./usecases/complaint/... + go test -cover ./usecases/complaint_activity/... + go test -cover ./usecases/complaint_file/... + go test -cover ./usecases/complaint_like/... + go test -cover ./usecases/complaint_process/... + go test -cover ./usecases/dashboard/... + go test -cover ./usecases/discussion/... + go test -cover ./usecases/news/... + go test -cover ./usecases/news_comment/... + go test -cover ./usecases/news_file/... + go test -cover ./usecases/news_like/... + go test -cover ./usecases/regency/... + go test -cover ./usecases/user/... + + - name: Check coverage + run: | + admin_coverage=$(go test -cover ./usecases/admin/... | grep -o '[0-9.]\+%' | cut -d'.' -f1 | tr -d '%') + category_coverage=$(go test -cover ./usecases/category/... | grep -o '[0-9.]\+%' | cut -d'.' -f1 | tr -d '%') + chatbot_coverage=$(go test -cover ./usecases/chatbot/... | grep -o '[0-9.]\+%' | cut -d'.' -f1 | tr -d '%') + complaint_coverage=$(go test -cover ./usecases/complaint/... | grep -o '[0-9.]\+%' | cut -d'.' -f1 | tr -d '%') + complaint_activity_coverage=$(go test -cover ./usecases/complaint_activity/... | grep -o '[0-9.]\+%' | cut -d'.' -f1 | tr -d '%') + complaint_file_coverage=$(go test -cover ./usecases/complaint_file/... | grep -o '[0-9.]\+%' | cut -d'.' -f1 | tr -d '%') + complaint_like_coverage=$(go test -cover ./usecases/complaint_like/... | grep -o '[0-9.]\+%' | cut -d'.' -f1 | tr -d '%') + complaint_process_coverage=$(go test -cover ./usecases/complaint_process/... | grep -o '[0-9.]\+%' | cut -d'.' -f1 | tr -d '%') + dashboard_coverage=$(go test -cover ./usecases/dashboard/... | grep -o '[0-9.]\+%' | cut -d'.' -f1 | tr -d '%') + discussion_coverage=$(go test -cover ./usecases/discussion/... | grep -o '[0-9.]\+%' | cut -d'.' -f1 | tr -d '%') + news_coverage=$(go test -cover ./usecases/news/... | grep -o '[0-9.]\+%' | cut -d'.' -f1 | tr -d '%') + news_comment_coverage=$(go test -cover ./usecases/news_comment/... | grep -o '[0-9.]\+%' | cut -d'.' -f1 | tr -d '%') + news_file_coverage=$(go test -cover ./usecases/news_file/... | grep -o '[0-9.]\+%' | cut -d'.' -f1 | tr -d '%') + news_like_coverage=$(go test -cover ./usecases/news_like/... | grep -o '[0-9.]\+%' | cut -d'.' -f1 | tr -d '%') + regency_coverage=$(go test -cover ./usecases/regency/... | grep -o '[0-9.]\+%' | cut -d'.' -f1 | tr -d '%') + user_coverage=$(go test -cover ./usecases/user/... | grep -o '[0-9.]\+%' | cut -d'.' -f1 | tr -d '%') + + if [ $admin_coverage -ge 90 ] && [ $category_coverage -ge 90 ] && [ $chatbot_coverage -ge 90 ] && [ $complaint_coverage -ge 90 ] && [ $complaint_activity_coverage -ge 90 ] && [ $complaint_file_coverage -ge 90 ] && [ $complaint_like_coverage -ge 90 ] && [ $complaint_process_coverage -ge 90 ] && [ $dashboard_coverage -ge 90 ] && [ $discussion_coverage -ge 90 ] && [ $news_coverage -ge 90 ] && [ $news_comment_coverage -ge 90 ] && [ $news_file_coverage -ge 90 ] && [ $news_like_coverage -ge 90 ] && [ $regency_coverage -ge 90 ] && [ $user_coverage -ge 90 ]; then + echo "All services have coverage above 90%" + else + echo "Some services have coverage below 90%" + exit 1 + fi \ No newline at end of file diff --git a/README.md b/README.md index 519309c..bb1adad 100644 Binary files a/README.md and b/README.md differ diff --git a/constants/errror.go b/constants/errror.go index de42a80..e014a91 100644 --- a/constants/errror.go +++ b/constants/errror.go @@ -47,4 +47,10 @@ var ( ErrInvalidFileFormat = errors.New("invalid file format") ErrForgotPasswordOTPNotVerified = errors.New("forgot password otp not verified") ErrConfirmPasswordDoesntMatch = errors.New("confirm password doesn't match") + ErrColumnsDoesntMatch = errors.New("columns doesn't match") + ErrInvalidCategoryIDFormat = errors.New("invalid category id format") + ErrEmailNotRegistered = errors.New("email not registered") + ErrDiscussionNotFound = errors.New("discussion not found") + ErrPasswordMustBeAtLeast8Characters = errors.New("password must be at least 8 characters") + ErrCategoryHasBeenUsed = errors.New("category has been used") ) diff --git a/controllers/category/category.go b/controllers/category/category.go index 66f1439..406c239 100644 --- a/controllers/category/category.go +++ b/controllers/category/category.go @@ -28,7 +28,12 @@ func (cc *CategoryController) GetAll(c echo.Context) error { return c.JSON(utils.ConvertResponseCode(err), base.NewErrorResponse(err.Error())) } - return c.JSON(http.StatusOK, base.NewSuccessResponse("Success get all categories", categories)) + responseCategories := make([]*response.Get, len(categories)) + for i, category := range categories { + responseCategories[i] = response.GetFromEntitiesToResponse(&category) + } + + return c.JSON(http.StatusOK, base.NewSuccessResponse("Success get all categories", responseCategories)) } func (cc *CategoryController) GetByID(c echo.Context) error { @@ -42,10 +47,6 @@ func (cc *CategoryController) GetByID(c echo.Context) error { return c.JSON(http.StatusInternalServerError, base.NewErrorResponse(err.Error())) } - if err != nil { - return c.JSON(http.StatusBadRequest, base.NewErrorResponse(err.Error())) - } - category, err := cc.useCase.GetByID(id) if err != nil { if errors.Is(err, constants.ErrCategoryNotFound) { @@ -54,7 +55,9 @@ func (cc *CategoryController) GetByID(c echo.Context) error { return c.JSON(utils.ConvertResponseCode(err), base.NewErrorResponse(err.Error())) } - return c.JSON(http.StatusOK, base.NewSuccessResponse("Success get category by ID", category)) + responseCategory := response.GetFromEntitiesToResponse(&category) + + return c.JSON(http.StatusOK, base.NewSuccessResponse("Success get category by ID", responseCategory)) } func (cc *CategoryController) CreateCategory(c echo.Context) error { diff --git a/controllers/dashboard/dashboard.go b/controllers/dashboard/dashboard.go index 1ea539c..6e3ff50 100644 --- a/controllers/dashboard/dashboard.go +++ b/controllers/dashboard/dashboard.go @@ -6,7 +6,6 @@ import ( "e-complaint-api/usecases/dashboard" "github.com/labstack/echo/v4" "net/http" - "time" ) type DashboardController struct { @@ -31,7 +30,7 @@ func (ctrl *DashboardController) GetDashboardData(c echo.Context) error { Name: complaint.User.Name, }, Complaint: response.Complaint{ - Date: complaint.Date.Format(time.RFC3339), + Date: complaint.CreatedAt.Format("2 January 2006 15:04:05"), Status: complaint.Status, }, Category: response.Category{ diff --git a/controllers/user/request/update_password.go b/controllers/user/request/update_password.go index 480841e..bddbe84 100644 --- a/controllers/user/request/update_password.go +++ b/controllers/user/request/update_password.go @@ -1,10 +1,5 @@ package request type UpdatePassword struct { - NewPassword string `json:"new_password" form:"new_password"` - ConfirmNewPassword string `json:"confirm_new_password" form:"confirm_new_password"` -} - -func (up *UpdatePassword) ToEntities() (string, string) { - return up.ConfirmNewPassword, up.NewPassword + NewPassword string `json:"new_password" form:"new_password"` } diff --git a/controllers/user/user.go b/controllers/user/user.go index 753cfb9..c94136a 100644 --- a/controllers/user/user.go +++ b/controllers/user/user.go @@ -66,18 +66,9 @@ func (uc *UserController) GetUserByID(c echo.Context) error { return c.JSON(http.StatusBadRequest, base.NewErrorResponse(constants.ErrInvalidIDFormat.Error())) } - jwtID, err := utils.GetIDFromJWT(c) - if err != nil { - return c.JSON(http.StatusInternalServerError, base.NewErrorResponse(err.Error())) - } - - if id != jwtID { - return c.JSON(http.StatusUnauthorized, base.NewErrorResponse(constants.ErrUnauthorized.Error())) - } - user, err := uc.userUseCase.GetUserByID(id) if err != nil { - return c.JSON(http.StatusInternalServerError, base.NewErrorResponse(err.Error())) + return c.JSON(utils.ConvertResponseCode(err), base.NewErrorResponse(err.Error())) } userResponse := response.GetUsersFromEntitiesToResponse(user) @@ -159,7 +150,7 @@ func (uc *UserController) DeleteUser(c echo.Context) error { err = uc.userUseCase.Delete(id) if err != nil { - return c.JSON(http.StatusInternalServerError, base.NewErrorResponse(err.Error())) + return c.JSON(utils.ConvertResponseCode(err), base.NewErrorResponse(err.Error())) } return c.JSON(http.StatusOK, base.NewSuccessResponse("Success Delete User", nil)) @@ -171,22 +162,12 @@ func (uc *UserController) UpdatePassword(c echo.Context) error { return c.JSON(http.StatusInternalServerError, base.NewErrorResponse(err.Error())) } - userRole, err := utils.GetRoleFromJWT(c) - if err != nil { - return c.JSON(http.StatusInternalServerError, base.NewErrorResponse(err.Error())) - } - - if userRole != "user" { - return c.JSON(http.StatusUnauthorized, base.NewErrorResponse(constants.ErrUnauthorized.Error())) - } - var passwordRequest request.UpdatePassword if err := c.Bind(&passwordRequest); err != nil { return c.JSON(http.StatusBadRequest, base.NewErrorResponse(err.Error())) } - newPassword, confirmPassword := passwordRequest.ToEntities() - err = uc.userUseCase.UpdatePassword(jwtID, newPassword, confirmPassword) + err = uc.userUseCase.UpdatePassword(jwtID, passwordRequest.NewPassword) if err != nil { return c.JSON(http.StatusBadRequest, base.NewErrorResponse(err.Error())) } diff --git a/cover.out b/cover.out new file mode 100644 index 0000000..ec45f4f --- /dev/null +++ b/cover.out @@ -0,0 +1,532 @@ +mode: set +e-complaint-api/usecases/category/category.go:13.85,15.2 1 1 +e-complaint-api/usecases/category/category.go:17.66,19.16 2 1 +e-complaint-api/usecases/category/category.go:19.16,21.3 1 1 +e-complaint-api/usecases/category/category.go:23.2,23.24 1 1 +e-complaint-api/usecases/category/category.go:26.71,28.16 2 1 +e-complaint-api/usecases/category/category.go:28.16,29.52 1 1 +e-complaint-api/usecases/category/category.go:29.52,31.4 1 1 +e-complaint-api/usecases/category/category.go:32.3,32.63 1 1 +e-complaint-api/usecases/category/category.go:35.2,35.22 1 1 +e-complaint-api/usecases/category/category.go:38.100,40.16 2 1 +e-complaint-api/usecases/category/category.go:40.16,42.3 1 1 +e-complaint-api/usecases/category/category.go:44.2,44.21 1 1 +e-complaint-api/usecases/category/category.go:44.21,46.3 1 1 +e-complaint-api/usecases/category/category.go:48.2,48.55 1 1 +e-complaint-api/usecases/category/category.go:48.55,50.3 1 1 +e-complaint-api/usecases/category/category.go:52.2,52.22 1 1 +e-complaint-api/usecases/category/category.go:55.111,58.16 2 1 +e-complaint-api/usecases/category/category.go:58.16,60.3 1 1 +e-complaint-api/usecases/category/category.go:62.2,62.61 1 1 +e-complaint-api/usecases/category/category.go:62.61,64.3 1 1 +e-complaint-api/usecases/category/category.go:66.2,66.28 1 1 +e-complaint-api/usecases/category/category.go:66.28,68.3 1 1 +e-complaint-api/usecases/category/category.go:70.2,70.35 1 1 +e-complaint-api/usecases/category/category.go:70.35,72.3 1 1 +e-complaint-api/usecases/category/category.go:74.2,74.106 1 1 +e-complaint-api/usecases/category/category.go:74.106,76.3 1 1 +e-complaint-api/usecases/category/category.go:78.2,79.16 2 1 +e-complaint-api/usecases/category/category.go:79.16,81.3 1 1 +e-complaint-api/usecases/category/category.go:83.2,83.29 1 1 +e-complaint-api/usecases/category/category.go:86.57,88.16 2 1 +e-complaint-api/usecases/category/category.go:88.16,90.3 1 1 +e-complaint-api/usecases/category/category.go:91.2,92.16 2 1 +e-complaint-api/usecases/category/category.go:92.16,94.3 1 1 +e-complaint-api/usecases/category/category.go:95.2,95.12 1 1 +e-complaint-api/usecases/chatbot/chatbot.go:15.229,22.2 1 1 +e-complaint-api/usecases/chatbot/chatbot.go:24.77,26.16 2 1 +e-complaint-api/usecases/chatbot/chatbot.go:26.16,28.3 1 1 +e-complaint-api/usecases/chatbot/chatbot.go:30.2,31.16 2 1 +e-complaint-api/usecases/chatbot/chatbot.go:31.16,33.3 1 1 +e-complaint-api/usecases/chatbot/chatbot.go:35.2,37.24 3 1 +e-complaint-api/usecases/chatbot/chatbot.go:37.24,41.3 3 1 +e-complaint-api/usecases/chatbot/chatbot.go:42.2,44.26 2 1 +e-complaint-api/usecases/chatbot/chatbot.go:44.26,46.35 2 1 +e-complaint-api/usecases/chatbot/chatbot.go:46.35,50.4 3 1 +e-complaint-api/usecases/chatbot/chatbot.go:51.3,51.35 1 1 +e-complaint-api/usecases/chatbot/chatbot.go:54.2,57.16 3 1 +e-complaint-api/usecases/chatbot/chatbot.go:57.16,59.3 1 1 +e-complaint-api/usecases/chatbot/chatbot.go:61.2,64.16 3 1 +e-complaint-api/usecases/chatbot/chatbot.go:64.16,66.3 1 1 +e-complaint-api/usecases/chatbot/chatbot.go:68.2,68.12 1 1 +e-complaint-api/usecases/chatbot/chatbot.go:71.77,73.16 2 1 +e-complaint-api/usecases/chatbot/chatbot.go:73.16,75.3 1 1 +e-complaint-api/usecases/chatbot/chatbot.go:77.2,77.22 1 1 +e-complaint-api/usecases/chatbot/chatbot.go:80.57,82.16 2 1 +e-complaint-api/usecases/chatbot/chatbot.go:82.16,84.3 1 1 +e-complaint-api/usecases/chatbot/chatbot.go:86.2,86.12 1 1 +e-complaint-api/usecases/admin/admin.go:15.82,19.2 1 1 +e-complaint-api/usecases/admin/admin.go:21.85,22.98 1 1 +e-complaint-api/usecases/admin/admin.go:22.98,24.3 1 1 +e-complaint-api/usecases/admin/admin.go:26.2,26.29 1 1 +e-complaint-api/usecases/admin/admin.go:26.29,28.3 1 1 +e-complaint-api/usecases/admin/admin.go:30.2,32.16 2 1 +e-complaint-api/usecases/admin/admin.go:32.16,33.47 1 1 +e-complaint-api/usecases/admin/admin.go:33.47,35.4 1 1 +e-complaint-api/usecases/admin/admin.go:35.9,35.57 1 1 +e-complaint-api/usecases/admin/admin.go:35.57,37.4 1 1 +e-complaint-api/usecases/admin/admin.go:37.9,39.4 1 1 +e-complaint-api/usecases/admin/admin.go:42.2,42.20 1 1 +e-complaint-api/usecases/admin/admin.go:45.77,46.47 1 1 +e-complaint-api/usecases/admin/admin.go:46.47,48.3 1 1 +e-complaint-api/usecases/admin/admin.go:50.2,51.16 2 1 +e-complaint-api/usecases/admin/admin.go:51.16,53.3 1 1 +e-complaint-api/usecases/admin/admin.go:55.2,55.24 1 1 +e-complaint-api/usecases/admin/admin.go:55.24,57.3 1 1 +e-complaint-api/usecases/admin/admin.go:57.8,59.3 1 1 +e-complaint-api/usecases/admin/admin.go:61.2,61.20 1 1 +e-complaint-api/usecases/admin/admin.go:64.65,66.16 2 1 +e-complaint-api/usecases/admin/admin.go:66.16,68.3 1 1 +e-complaint-api/usecases/admin/admin.go:70.2,71.34 2 1 +e-complaint-api/usecases/admin/admin.go:71.34,73.3 1 1 +e-complaint-api/usecases/admin/admin.go:75.2,75.25 1 1 +e-complaint-api/usecases/admin/admin.go:78.70,80.18 2 1 +e-complaint-api/usecases/admin/admin.go:80.18,82.3 1 1 +e-complaint-api/usecases/admin/admin.go:84.2,84.19 1 1 +e-complaint-api/usecases/admin/admin.go:87.50,90.18 2 1 +e-complaint-api/usecases/admin/admin.go:90.18,92.3 1 1 +e-complaint-api/usecases/admin/admin.go:94.2,95.16 2 1 +e-complaint-api/usecases/admin/admin.go:95.16,97.3 1 1 +e-complaint-api/usecases/admin/admin.go:99.2,99.12 1 1 +e-complaint-api/usecases/admin/admin.go:102.91,104.16 2 1 +e-complaint-api/usecases/admin/admin.go:104.16,105.49 1 1 +e-complaint-api/usecases/admin/admin.go:105.49,107.4 1 1 +e-complaint-api/usecases/admin/admin.go:108.3,108.60 1 1 +e-complaint-api/usecases/admin/admin.go:112.2,112.61 1 1 +e-complaint-api/usecases/admin/admin.go:112.61,114.73 2 1 +e-complaint-api/usecases/admin/admin.go:114.73,116.4 1 1 +e-complaint-api/usecases/admin/admin.go:119.2,122.58 2 1 +e-complaint-api/usecases/admin/admin.go:122.58,125.3 2 1 +e-complaint-api/usecases/admin/admin.go:126.2,126.61 1 1 +e-complaint-api/usecases/admin/admin.go:126.61,129.3 2 1 +e-complaint-api/usecases/admin/admin.go:130.2,130.91 1 1 +e-complaint-api/usecases/admin/admin.go:130.91,133.3 2 1 +e-complaint-api/usecases/admin/admin.go:134.2,134.26 1 1 +e-complaint-api/usecases/admin/admin.go:134.26,137.3 2 1 +e-complaint-api/usecases/admin/admin.go:139.2,139.16 1 1 +e-complaint-api/usecases/admin/admin.go:139.16,141.3 1 1 +e-complaint-api/usecases/admin/admin.go:143.2,143.29 1 1 +e-complaint-api/usecases/admin/admin.go:143.29,145.3 1 1 +e-complaint-api/usecases/admin/admin.go:147.2,148.16 2 1 +e-complaint-api/usecases/admin/admin.go:148.16,150.3 1 1 +e-complaint-api/usecases/admin/admin.go:152.2,152.28 1 1 +e-complaint-api/usecases/complaint_activity/complaint_activity.go:11.112,15.2 1 1 +e-complaint-api/usecases/complaint_activity/complaint_activity.go:17.136,19.16 2 1 +e-complaint-api/usecases/complaint_activity/complaint_activity.go:19.16,21.3 1 1 +e-complaint-api/usecases/complaint_activity/complaint_activity.go:23.2,23.33 1 1 +e-complaint-api/usecases/complaint_activity/complaint_activity.go:26.126,28.16 2 1 +e-complaint-api/usecases/complaint_activity/complaint_activity.go:28.16,30.3 1 1 +e-complaint-api/usecases/complaint_activity/complaint_activity.go:32.2,32.32 1 1 +e-complaint-api/usecases/complaint_activity/complaint_activity.go:35.95,37.16 2 1 +e-complaint-api/usecases/complaint_activity/complaint_activity.go:37.16,39.3 1 1 +e-complaint-api/usecases/complaint_activity/complaint_activity.go:41.2,41.12 1 1 +e-complaint-api/usecases/complaint_activity/complaint_activity.go:44.95,46.16 2 1 +e-complaint-api/usecases/complaint_activity/complaint_activity.go:46.16,48.3 1 1 +e-complaint-api/usecases/complaint_activity/complaint_activity.go:50.2,50.12 1 1 +e-complaint-api/usecases/complaint/complaint.go:19.158,25.2 1 1 +e-complaint-api/usecases/complaint/complaint.go:27.170,28.29 1 1 +e-complaint-api/usecases/complaint/complaint.go:28.29,30.3 1 1 +e-complaint-api/usecases/complaint/complaint.go:30.8,30.36 1 1 +e-complaint-api/usecases/complaint/complaint.go:30.36,32.3 1 1 +e-complaint-api/usecases/complaint/complaint.go:34.2,34.18 1 1 +e-complaint-api/usecases/complaint/complaint.go:34.18,36.3 1 1 +e-complaint-api/usecases/complaint/complaint.go:38.2,38.20 1 1 +e-complaint-api/usecases/complaint/complaint.go:38.20,40.3 1 1 +e-complaint-api/usecases/complaint/complaint.go:42.2,43.16 2 1 +e-complaint-api/usecases/complaint/complaint.go:43.16,45.3 1 1 +e-complaint-api/usecases/complaint/complaint.go:47.2,47.24 1 1 +e-complaint-api/usecases/complaint/complaint.go:50.134,54.16 3 1 +e-complaint-api/usecases/complaint/complaint.go:54.16,56.3 1 1 +e-complaint-api/usecases/complaint/complaint.go:58.2,58.29 1 1 +e-complaint-api/usecases/complaint/complaint.go:58.29,62.30 4 1 +e-complaint-api/usecases/complaint/complaint.go:62.30,65.4 2 1 +e-complaint-api/usecases/complaint/complaint.go:65.9,65.59 1 1 +e-complaint-api/usecases/complaint/complaint.go:65.59,67.4 1 1 +e-complaint-api/usecases/complaint/complaint.go:67.9,69.4 1 1 +e-complaint-api/usecases/complaint/complaint.go:71.3,71.15 1 1 +e-complaint-api/usecases/complaint/complaint.go:71.15,73.4 1 1 +e-complaint-api/usecases/complaint/complaint.go:73.9,75.4 1 1 +e-complaint-api/usecases/complaint/complaint.go:77.3,77.33 1 1 +e-complaint-api/usecases/complaint/complaint.go:77.33,79.4 1 1 +e-complaint-api/usecases/complaint/complaint.go:79.9,81.4 1 1 +e-complaint-api/usecases/complaint/complaint.go:82.8,89.3 6 1 +e-complaint-api/usecases/complaint/complaint.go:90.2,92.22 2 1 +e-complaint-api/usecases/complaint/complaint.go:95.75,97.16 2 1 +e-complaint-api/usecases/complaint/complaint.go:97.16,99.3 1 1 +e-complaint-api/usecases/complaint/complaint.go:101.2,101.23 1 1 +e-complaint-api/usecases/complaint/complaint.go:104.82,106.16 2 1 +e-complaint-api/usecases/complaint/complaint.go:106.16,108.3 1 1 +e-complaint-api/usecases/complaint/complaint.go:110.2,110.24 1 1 +e-complaint-api/usecases/complaint/complaint.go:113.94,114.194 1 1 +e-complaint-api/usecases/complaint/complaint.go:114.194,116.3 1 1 +e-complaint-api/usecases/complaint/complaint.go:117.2,120.16 3 1 +e-complaint-api/usecases/complaint/complaint.go:120.16,121.71 1 1 +e-complaint-api/usecases/complaint/complaint.go:121.71,123.4 1 1 +e-complaint-api/usecases/complaint/complaint.go:123.9,123.79 1 1 +e-complaint-api/usecases/complaint/complaint.go:123.79,125.4 1 1 +e-complaint-api/usecases/complaint/complaint.go:125.9,127.4 1 1 +e-complaint-api/usecases/complaint/complaint.go:130.2,130.24 1 1 +e-complaint-api/usecases/complaint/complaint.go:133.77,134.46 1 1 +e-complaint-api/usecases/complaint/complaint.go:134.46,136.17 2 1 +e-complaint-api/usecases/complaint/complaint.go:136.17,138.4 1 1 +e-complaint-api/usecases/complaint/complaint.go:139.8,141.17 2 1 +e-complaint-api/usecases/complaint/complaint.go:141.17,143.4 1 1 +e-complaint-api/usecases/complaint/complaint.go:146.2,146.12 1 1 +e-complaint-api/usecases/complaint/complaint.go:149.93,150.194 1 1 +e-complaint-api/usecases/complaint/complaint.go:150.194,152.3 1 1 +e-complaint-api/usecases/complaint/complaint.go:154.2,155.16 2 1 +e-complaint-api/usecases/complaint/complaint.go:155.16,156.71 1 1 +e-complaint-api/usecases/complaint/complaint.go:156.71,158.4 1 1 +e-complaint-api/usecases/complaint/complaint.go:158.9,158.79 1 1 +e-complaint-api/usecases/complaint/complaint.go:158.79,160.4 1 1 +e-complaint-api/usecases/complaint/complaint.go:160.9,162.4 1 1 +e-complaint-api/usecases/complaint/complaint.go:165.2,165.23 1 1 +e-complaint-api/usecases/complaint/complaint.go:168.73,169.124 1 1 +e-complaint-api/usecases/complaint/complaint.go:169.124,171.3 1 1 +e-complaint-api/usecases/complaint/complaint.go:173.2,173.14 1 1 +e-complaint-api/usecases/complaint/complaint.go:173.14,175.3 1 1 +e-complaint-api/usecases/complaint/complaint.go:177.2,178.16 2 1 +e-complaint-api/usecases/complaint/complaint.go:178.16,180.3 1 1 +e-complaint-api/usecases/complaint/complaint.go:182.2,182.12 1 1 +e-complaint-api/usecases/complaint/complaint.go:185.69,187.16 2 1 +e-complaint-api/usecases/complaint/complaint.go:187.16,189.3 1 1 +e-complaint-api/usecases/complaint/complaint.go:191.2,196.27 4 1 +e-complaint-api/usecases/complaint/complaint.go:196.27,197.13 1 1 +e-complaint-api/usecases/complaint/complaint.go:197.13,199.12 1 1 +e-complaint-api/usecases/complaint/complaint.go:202.3,202.19 1 1 +e-complaint-api/usecases/complaint/complaint.go:202.19,205.4 1 1 +e-complaint-api/usecases/complaint/complaint.go:207.3,208.17 2 1 +e-complaint-api/usecases/complaint/complaint.go:208.17,210.4 1 1 +e-complaint-api/usecases/complaint/complaint.go:212.3,213.17 2 1 +e-complaint-api/usecases/complaint/complaint.go:213.17,215.4 1 1 +e-complaint-api/usecases/complaint/complaint.go:217.3,226.29 9 1 +e-complaint-api/usecases/complaint/complaint.go:226.29,237.4 2 1 +e-complaint-api/usecases/complaint/complaint.go:237.9,237.37 1 1 +e-complaint-api/usecases/complaint/complaint.go:237.37,253.4 3 1 +e-complaint-api/usecases/complaint/complaint.go:253.9,253.33 1 1 +e-complaint-api/usecases/complaint/complaint.go:253.33,274.4 4 1 +e-complaint-api/usecases/complaint/complaint.go:274.9,274.33 1 1 +e-complaint-api/usecases/complaint/complaint.go:274.33,285.4 2 1 +e-complaint-api/usecases/complaint/complaint.go:285.9,285.33 1 1 +e-complaint-api/usecases/complaint/complaint.go:285.33,291.4 1 1 +e-complaint-api/usecases/complaint/complaint.go:294.3,296.30 3 1 +e-complaint-api/usecases/complaint/complaint.go:296.30,302.4 2 1 +e-complaint-api/usecases/complaint/complaint.go:304.3,318.45 2 1 +e-complaint-api/usecases/complaint/complaint.go:322.2,323.16 2 1 +e-complaint-api/usecases/complaint/complaint.go:323.16,325.3 1 1 +e-complaint-api/usecases/complaint/complaint.go:327.2,327.12 1 1 +e-complaint-api/usecases/complaint/complaint.go:330.64,332.16 2 1 +e-complaint-api/usecases/complaint/complaint.go:332.16,334.3 1 1 +e-complaint-api/usecases/complaint/complaint.go:336.2,336.12 1 1 +e-complaint-api/usecases/complaint/complaint.go:339.64,341.16 2 1 +e-complaint-api/usecases/complaint/complaint.go:341.16,343.3 1 1 +e-complaint-api/usecases/complaint/complaint.go:345.2,345.12 1 1 +e-complaint-api/usecases/complaint/complaint.go:348.82,350.16 2 1 +e-complaint-api/usecases/complaint/complaint.go:350.16,352.3 1 1 +e-complaint-api/usecases/complaint/complaint.go:354.2,354.26 1 1 +e-complaint-api/usecases/complaint_file/complaint_file.go:13.153,18.2 1 1 +e-complaint-api/usecases/complaint_file/complaint_file.go:20.124,22.23 2 1 +e-complaint-api/usecases/complaint_file/complaint_file.go:22.23,24.3 1 1 +e-complaint-api/usecases/complaint_file/complaint_file.go:26.2,27.37 2 1 +e-complaint-api/usecases/complaint_file/complaint_file.go:27.37,33.3 2 1 +e-complaint-api/usecases/complaint_file/complaint_file.go:35.2,36.23 2 1 +e-complaint-api/usecases/complaint_file/complaint_file.go:36.23,38.3 1 1 +e-complaint-api/usecases/complaint_file/complaint_file.go:40.2,41.36 2 1 +e-complaint-api/usecases/complaint_file/complaint_file.go:41.36,43.3 1 1 +e-complaint-api/usecases/complaint_file/complaint_file.go:45.2,45.37 1 1 +e-complaint-api/usecases/complaint_file/complaint_file.go:48.78,50.23 2 1 +e-complaint-api/usecases/complaint_file/complaint_file.go:50.23,52.3 1 1 +e-complaint-api/usecases/complaint_file/complaint_file.go:54.2,54.12 1 1 +e-complaint-api/usecases/complaint_like/complaint_like.go:11.100,15.2 1 1 +e-complaint-api/usecases/complaint_like/complaint_like.go:17.98,19.16 2 1 +e-complaint-api/usecases/complaint_like/complaint_like.go:19.16,21.3 1 1 +e-complaint-api/usecases/complaint_like/complaint_like.go:23.2,24.34 2 1 +e-complaint-api/usecases/complaint_like/complaint_like.go:24.34,27.3 2 1 +e-complaint-api/usecases/complaint_like/complaint_like.go:27.8,31.3 3 1 +e-complaint-api/usecases/complaint_like/complaint_like.go:33.2,33.16 1 1 +e-complaint-api/usecases/complaint_like/complaint_like.go:33.16,35.3 1 1 +e-complaint-api/usecases/complaint_like/complaint_like.go:37.2,37.24 1 1 +e-complaint-api/usecases/dashboard/dashboard.go:19.82,21.2 1 1 +e-complaint-api/usecases/dashboard/dashboard.go:23.65,25.2 1 1 +e-complaint-api/usecases/dashboard/dashboard.go:27.79,29.2 1 1 +e-complaint-api/usecases/dashboard/dashboard.go:31.95,33.2 1 1 +e-complaint-api/usecases/dashboard/dashboard.go:35.90,37.2 1 1 +e-complaint-api/usecases/discussion/discussion.go:15.191,21.2 1 1 +e-complaint-api/usecases/discussion/discussion.go:23.75,24.30 1 1 +e-complaint-api/usecases/discussion/discussion.go:24.30,26.3 1 1 +e-complaint-api/usecases/discussion/discussion.go:28.2,29.16 2 1 +e-complaint-api/usecases/discussion/discussion.go:29.16,31.3 1 1 +e-complaint-api/usecases/discussion/discussion.go:33.2,33.12 1 1 +e-complaint-api/usecases/discussion/discussion.go:36.75,38.16 2 1 +e-complaint-api/usecases/discussion/discussion.go:38.16,40.3 1 1 +e-complaint-api/usecases/discussion/discussion.go:42.2,42.24 1 1 +e-complaint-api/usecases/discussion/discussion.go:45.98,47.16 2 1 +e-complaint-api/usecases/discussion/discussion.go:47.16,49.3 1 1 +e-complaint-api/usecases/discussion/discussion.go:51.2,51.25 1 1 +e-complaint-api/usecases/discussion/discussion.go:54.75,55.30 1 1 +e-complaint-api/usecases/discussion/discussion.go:55.30,57.3 1 1 +e-complaint-api/usecases/discussion/discussion.go:58.2,59.16 2 1 +e-complaint-api/usecases/discussion/discussion.go:59.16,61.3 1 1 +e-complaint-api/usecases/discussion/discussion.go:63.2,63.12 1 1 +e-complaint-api/usecases/discussion/discussion.go:66.50,68.16 2 1 +e-complaint-api/usecases/discussion/discussion.go:68.16,70.3 1 1 +e-complaint-api/usecases/discussion/discussion.go:72.2,72.12 1 1 +e-complaint-api/usecases/discussion/discussion.go:75.89,77.16 2 1 +e-complaint-api/usecases/discussion/discussion.go:77.16,79.3 1 1 +e-complaint-api/usecases/discussion/discussion.go:81.2,82.16 2 1 +e-complaint-api/usecases/discussion/discussion.go:82.16,84.3 1 1 +e-complaint-api/usecases/discussion/discussion.go:86.2,88.24 3 1 +e-complaint-api/usecases/discussion/discussion.go:88.24,92.3 3 1 +e-complaint-api/usecases/discussion/discussion.go:93.2,95.24 2 1 +e-complaint-api/usecases/discussion/discussion.go:95.24,97.34 2 1 +e-complaint-api/usecases/discussion/discussion.go:97.34,98.23 1 1 +e-complaint-api/usecases/discussion/discussion.go:98.23,102.5 3 1 +e-complaint-api/usecases/discussion/discussion.go:102.10,106.5 3 1 +e-complaint-api/usecases/discussion/discussion.go:108.3,108.35 1 1 +e-complaint-api/usecases/discussion/discussion.go:111.2,114.16 3 1 +e-complaint-api/usecases/discussion/discussion.go:114.16,116.3 1 1 +e-complaint-api/usecases/discussion/discussion.go:118.2,118.25 1 1 +e-complaint-api/usecases/complaint_process/complaint_process.go:14.174,19.2 1 1 +e-complaint-api/usecases/complaint_process/complaint_process.go:21.122,22.69 1 1 +e-complaint-api/usecases/complaint_process/complaint_process.go:22.69,24.3 1 1 +e-complaint-api/usecases/complaint_process/complaint_process.go:26.2,26.209 1 1 +e-complaint-api/usecases/complaint_process/complaint_process.go:26.209,28.3 1 1 +e-complaint-api/usecases/complaint_process/complaint_process.go:30.2,31.16 2 1 +e-complaint-api/usecases/complaint_process/complaint_process.go:31.16,33.3 1 1 +e-complaint-api/usecases/complaint_process/complaint_process.go:35.2,35.42 1 1 +e-complaint-api/usecases/complaint_process/complaint_process.go:35.42,36.30 1 1 +e-complaint-api/usecases/complaint_process/complaint_process.go:36.30,38.4 1 1 +e-complaint-api/usecases/complaint_process/complaint_process.go:38.9,38.33 1 1 +e-complaint-api/usecases/complaint_process/complaint_process.go:38.33,40.4 1 1 +e-complaint-api/usecases/complaint_process/complaint_process.go:41.8,41.52 1 1 +e-complaint-api/usecases/complaint_process/complaint_process.go:41.52,42.29 1 1 +e-complaint-api/usecases/complaint_process/complaint_process.go:42.29,44.4 1 1 +e-complaint-api/usecases/complaint_process/complaint_process.go:44.9,44.33 1 1 +e-complaint-api/usecases/complaint_process/complaint_process.go:44.33,46.4 1 1 +e-complaint-api/usecases/complaint_process/complaint_process.go:46.9,46.33 1 1 +e-complaint-api/usecases/complaint_process/complaint_process.go:46.33,48.4 1 1 +e-complaint-api/usecases/complaint_process/complaint_process.go:48.9,48.37 1 1 +e-complaint-api/usecases/complaint_process/complaint_process.go:48.37,50.4 1 1 +e-complaint-api/usecases/complaint_process/complaint_process.go:51.8,51.53 1 1 +e-complaint-api/usecases/complaint_process/complaint_process.go:51.53,52.30 1 1 +e-complaint-api/usecases/complaint_process/complaint_process.go:52.30,54.4 1 1 +e-complaint-api/usecases/complaint_process/complaint_process.go:54.9,54.33 1 1 +e-complaint-api/usecases/complaint_process/complaint_process.go:54.33,56.4 1 1 +e-complaint-api/usecases/complaint_process/complaint_process.go:56.9,56.33 1 1 +e-complaint-api/usecases/complaint_process/complaint_process.go:56.33,58.4 1 1 +e-complaint-api/usecases/complaint_process/complaint_process.go:58.9,58.33 1 1 +e-complaint-api/usecases/complaint_process/complaint_process.go:58.33,60.4 1 1 +e-complaint-api/usecases/complaint_process/complaint_process.go:61.8,61.49 1 1 +e-complaint-api/usecases/complaint_process/complaint_process.go:61.49,62.26 1 1 +e-complaint-api/usecases/complaint_process/complaint_process.go:62.26,64.4 1 1 +e-complaint-api/usecases/complaint_process/complaint_process.go:64.9,64.33 1 1 +e-complaint-api/usecases/complaint_process/complaint_process.go:64.33,66.4 1 1 +e-complaint-api/usecases/complaint_process/complaint_process.go:66.9,66.33 1 1 +e-complaint-api/usecases/complaint_process/complaint_process.go:66.33,68.4 1 1 +e-complaint-api/usecases/complaint_process/complaint_process.go:68.9,68.36 1 1 +e-complaint-api/usecases/complaint_process/complaint_process.go:68.36,70.4 1 1 +e-complaint-api/usecases/complaint_process/complaint_process.go:71.8,71.49 1 1 +e-complaint-api/usecases/complaint_process/complaint_process.go:71.49,72.26 1 1 +e-complaint-api/usecases/complaint_process/complaint_process.go:72.26,74.4 1 1 +e-complaint-api/usecases/complaint_process/complaint_process.go:74.9,74.33 1 1 +e-complaint-api/usecases/complaint_process/complaint_process.go:74.33,76.4 1 1 +e-complaint-api/usecases/complaint_process/complaint_process.go:76.9,76.36 1 1 +e-complaint-api/usecases/complaint_process/complaint_process.go:76.36,78.4 1 1 +e-complaint-api/usecases/complaint_process/complaint_process.go:78.9,78.37 1 1 +e-complaint-api/usecases/complaint_process/complaint_process.go:78.37,80.4 1 1 +e-complaint-api/usecases/complaint_process/complaint_process.go:83.2,84.16 2 1 +e-complaint-api/usecases/complaint_process/complaint_process.go:84.16,85.70 1 1 +e-complaint-api/usecases/complaint_process/complaint_process.go:85.70,87.4 1 1 +e-complaint-api/usecases/complaint_process/complaint_process.go:87.9,89.4 1 1 +e-complaint-api/usecases/complaint_process/complaint_process.go:92.2,92.31 1 1 +e-complaint-api/usecases/complaint_process/complaint_process.go:95.109,97.16 2 1 +e-complaint-api/usecases/complaint_process/complaint_process.go:97.16,99.3 1 1 +e-complaint-api/usecases/complaint_process/complaint_process.go:101.2,101.32 1 1 +e-complaint-api/usecases/complaint_process/complaint_process.go:104.122,105.36 1 1 +e-complaint-api/usecases/complaint_process/complaint_process.go:105.36,107.3 1 1 +e-complaint-api/usecases/complaint_process/complaint_process.go:109.2,110.16 2 1 +e-complaint-api/usecases/complaint_process/complaint_process.go:110.16,112.3 1 1 +e-complaint-api/usecases/complaint_process/complaint_process.go:114.2,114.31 1 1 +e-complaint-api/usecases/complaint_process/complaint_process.go:117.102,118.50 1 1 +e-complaint-api/usecases/complaint_process/complaint_process.go:118.50,120.3 1 1 +e-complaint-api/usecases/complaint_process/complaint_process.go:122.2,123.16 2 1 +e-complaint-api/usecases/complaint_process/complaint_process.go:123.16,125.3 1 1 +e-complaint-api/usecases/complaint_process/complaint_process.go:127.2,127.28 1 1 +e-complaint-api/usecases/complaint_process/complaint_process.go:127.28,129.3 1 1 +e-complaint-api/usecases/complaint_process/complaint_process.go:129.8,129.36 1 1 +e-complaint-api/usecases/complaint_process/complaint_process.go:129.36,131.3 1 1 +e-complaint-api/usecases/complaint_process/complaint_process.go:131.8,131.32 1 1 +e-complaint-api/usecases/complaint_process/complaint_process.go:131.32,133.3 1 1 +e-complaint-api/usecases/complaint_process/complaint_process.go:133.8,133.32 1 1 +e-complaint-api/usecases/complaint_process/complaint_process.go:133.32,135.3 1 1 +e-complaint-api/usecases/complaint_process/complaint_process.go:137.2,137.20 1 1 +e-complaint-api/usecases/news_comment/news_comment.go:12.94,16.2 1 1 +e-complaint-api/usecases/news_comment/news_comment.go:18.85,21.31 2 1 +e-complaint-api/usecases/news_comment/news_comment.go:21.31,23.3 1 1 +e-complaint-api/usecases/news_comment/news_comment.go:25.2,25.16 1 1 +e-complaint-api/usecases/news_comment/news_comment.go:25.16,27.3 1 1 +e-complaint-api/usecases/news_comment/news_comment.go:29.2,29.12 1 1 +e-complaint-api/usecases/news_comment/news_comment.go:32.79,34.16 2 1 +e-complaint-api/usecases/news_comment/news_comment.go:34.16,36.3 1 1 +e-complaint-api/usecases/news_comment/news_comment.go:38.2,38.25 1 1 +e-complaint-api/usecases/news_comment/news_comment.go:41.88,43.16 2 1 +e-complaint-api/usecases/news_comment/news_comment.go:43.16,45.3 1 1 +e-complaint-api/usecases/news_comment/news_comment.go:47.2,47.25 1 1 +e-complaint-api/usecases/news_comment/news_comment.go:50.87,52.16 2 1 +e-complaint-api/usecases/news_comment/news_comment.go:52.16,54.3 1 1 +e-complaint-api/usecases/news_comment/news_comment.go:56.2,56.12 1 1 +e-complaint-api/usecases/news_comment/news_comment.go:59.60,61.16 2 1 +e-complaint-api/usecases/news_comment/news_comment.go:61.16,63.3 1 1 +e-complaint-api/usecases/news_comment/news_comment.go:65.2,65.12 1 1 +e-complaint-api/usecases/news/news.go:13.79,17.2 1 1 +e-complaint-api/usecases/news/news.go:19.160,20.29 1 1 +e-complaint-api/usecases/news/news.go:20.29,22.3 1 1 +e-complaint-api/usecases/news/news.go:22.8,22.36 1 1 +e-complaint-api/usecases/news/news.go:22.36,24.3 1 1 +e-complaint-api/usecases/news/news.go:26.2,26.18 1 1 +e-complaint-api/usecases/news/news.go:26.18,28.3 1 1 +e-complaint-api/usecases/news/news.go:30.2,30.20 1 1 +e-complaint-api/usecases/news/news.go:30.20,32.3 1 1 +e-complaint-api/usecases/news/news.go:34.2,35.16 2 1 +e-complaint-api/usecases/news/news.go:35.16,37.3 1 1 +e-complaint-api/usecases/news/news.go:39.2,39.18 1 1 +e-complaint-api/usecases/news/news.go:42.129,46.16 3 1 +e-complaint-api/usecases/news/news.go:46.16,48.3 1 1 +e-complaint-api/usecases/news/news.go:50.2,50.29 1 1 +e-complaint-api/usecases/news/news.go:50.29,54.52 4 1 +e-complaint-api/usecases/news/news.go:54.52,56.4 1 1 +e-complaint-api/usecases/news/news.go:56.9,58.4 1 1 +e-complaint-api/usecases/news/news.go:60.3,60.15 1 1 +e-complaint-api/usecases/news/news.go:60.15,62.4 1 1 +e-complaint-api/usecases/news/news.go:62.9,64.4 1 1 +e-complaint-api/usecases/news/news.go:66.3,66.33 1 1 +e-complaint-api/usecases/news/news.go:66.33,68.4 1 1 +e-complaint-api/usecases/news/news.go:68.9,70.4 1 1 +e-complaint-api/usecases/news/news.go:71.8,78.3 6 1 +e-complaint-api/usecases/news/news.go:80.2,82.22 2 1 +e-complaint-api/usecases/news/news.go:85.62,87.16 2 1 +e-complaint-api/usecases/news/news.go:87.16,89.3 1 1 +e-complaint-api/usecases/news/news.go:91.2,91.18 1 1 +e-complaint-api/usecases/news/news.go:94.74,95.68 1 1 +e-complaint-api/usecases/news/news.go:95.68,97.3 1 1 +e-complaint-api/usecases/news/news.go:99.2,100.16 2 1 +e-complaint-api/usecases/news/news.go:100.16,101.72 1 1 +e-complaint-api/usecases/news/news.go:101.72,103.4 1 1 +e-complaint-api/usecases/news/news.go:103.9,105.4 1 1 +e-complaint-api/usecases/news/news.go:108.2,108.19 1 1 +e-complaint-api/usecases/news/news.go:111.44,113.16 2 1 +e-complaint-api/usecases/news/news.go:113.16,115.3 1 1 +e-complaint-api/usecases/news/news.go:117.2,117.12 1 1 +e-complaint-api/usecases/news/news.go:120.73,121.68 1 1 +e-complaint-api/usecases/news/news.go:121.68,123.3 1 1 +e-complaint-api/usecases/news/news.go:125.2,126.16 2 1 +e-complaint-api/usecases/news/news.go:126.16,127.72 1 1 +e-complaint-api/usecases/news/news.go:127.72,129.4 1 1 +e-complaint-api/usecases/news/news.go:129.9,131.4 1 1 +e-complaint-api/usecases/news/news.go:134.2,134.18 1 1 +e-complaint-api/usecases/news_file/news_file.go:13.133,18.2 1 1 +e-complaint-api/usecases/news_file/news_file.go:20.106,22.23 2 1 +e-complaint-api/usecases/news_file/news_file.go:22.23,24.3 1 1 +e-complaint-api/usecases/news_file/news_file.go:26.2,27.37 2 1 +e-complaint-api/usecases/news_file/news_file.go:27.37,33.3 2 1 +e-complaint-api/usecases/news_file/news_file.go:35.2,36.23 2 1 +e-complaint-api/usecases/news_file/news_file.go:36.23,38.3 1 1 +e-complaint-api/usecases/news_file/news_file.go:40.2,41.31 2 1 +e-complaint-api/usecases/news_file/news_file.go:41.31,43.3 1 1 +e-complaint-api/usecases/news_file/news_file.go:45.2,45.32 1 1 +e-complaint-api/usecases/news_file/news_file.go:48.60,50.23 2 1 +e-complaint-api/usecases/news_file/news_file.go:50.23,52.3 1 1 +e-complaint-api/usecases/news_file/news_file.go:54.2,54.12 1 1 +e-complaint-api/usecases/news_like/news_like.go:9.85,13.2 1 1 +e-complaint-api/usecases/news_like/news_like.go:15.83,18.17 2 1 +e-complaint-api/usecases/news_like/news_like.go:18.17,20.17 2 1 +e-complaint-api/usecases/news_like/news_like.go:20.17,22.4 1 1 +e-complaint-api/usecases/news_like/news_like.go:24.3,24.22 1 1 +e-complaint-api/usecases/news_like/news_like.go:27.2,28.16 2 1 +e-complaint-api/usecases/news_like/news_like.go:28.16,30.3 1 1 +e-complaint-api/usecases/news_like/news_like.go:32.2,32.23 1 1 +e-complaint-api/usecases/news_like/news_like.go:35.63,37.16 2 1 +e-complaint-api/usecases/news_like/news_like.go:37.16,39.3 1 1 +e-complaint-api/usecases/news_like/news_like.go:41.2,41.12 1 1 +e-complaint-api/usecases/news_like/news_like.go:44.63,46.16 2 1 +e-complaint-api/usecases/news_like/news_like.go:46.16,48.3 1 1 +e-complaint-api/usecases/news_like/news_like.go:50.2,50.12 1 1 +e-complaint-api/usecases/regency/regency.go:12.82,14.2 1 1 +e-complaint-api/usecases/regency/regency.go:16.64,18.16 2 1 +e-complaint-api/usecases/regency/regency.go:18.16,20.3 1 1 +e-complaint-api/usecases/regency/regency.go:22.2,22.23 1 1 +e-complaint-api/usecases/user/user.go:19.160,25.2 1 1 +e-complaint-api/usecases/user/user.go:27.76,28.94 1 1 +e-complaint-api/usecases/user/user.go:28.94,30.3 1 1 +e-complaint-api/usecases/user/user.go:32.2,32.28 1 1 +e-complaint-api/usecases/user/user.go:32.28,34.3 1 1 +e-complaint-api/usecases/user/user.go:36.2,38.16 2 1 +e-complaint-api/usecases/user/user.go:38.16,39.51 1 1 +e-complaint-api/usecases/user/user.go:39.51,40.48 1 1 +e-complaint-api/usecases/user/user.go:40.48,42.5 1 1 +e-complaint-api/usecases/user/user.go:42.10,42.58 1 1 +e-complaint-api/usecases/user/user.go:42.58,44.5 1 1 +e-complaint-api/usecases/user/user.go:45.9,47.4 1 1 +e-complaint-api/usecases/user/user.go:50.2,50.19 1 1 +e-complaint-api/usecases/user/user.go:53.73,54.45 1 1 +e-complaint-api/usecases/user/user.go:54.45,56.3 1 1 +e-complaint-api/usecases/user/user.go:58.2,62.16 3 1 +e-complaint-api/usecases/user/user.go:62.16,64.3 1 1 +e-complaint-api/usecases/user/user.go:66.2,66.19 1 1 +e-complaint-api/usecases/user/user.go:69.63,72.16 2 1 +e-complaint-api/usecases/user/user.go:72.16,74.3 1 1 +e-complaint-api/usecases/user/user.go:76.2,76.19 1 1 +e-complaint-api/usecases/user/user.go:79.67,82.16 2 1 +e-complaint-api/usecases/user/user.go:82.16,84.3 1 1 +e-complaint-api/usecases/user/user.go:86.2,86.18 1 1 +e-complaint-api/usecases/user/user.go:89.86,90.71 1 1 +e-complaint-api/usecases/user/user.go:90.71,92.3 1 1 +e-complaint-api/usecases/user/user.go:94.2,95.16 2 1 +e-complaint-api/usecases/user/user.go:95.16,97.3 1 1 +e-complaint-api/usecases/user/user.go:99.2,104.16 5 1 +e-complaint-api/usecases/user/user.go:104.16,105.51 1 1 +e-complaint-api/usecases/user/user.go:105.51,106.48 1 1 +e-complaint-api/usecases/user/user.go:106.48,108.5 1 1 +e-complaint-api/usecases/user/user.go:108.10,108.58 1 1 +e-complaint-api/usecases/user/user.go:108.58,110.5 1 1 +e-complaint-api/usecases/user/user.go:111.9,113.4 1 1 +e-complaint-api/usecases/user/user.go:116.2,116.27 1 1 +e-complaint-api/usecases/user/user.go:119.92,121.16 2 1 +e-complaint-api/usecases/user/user.go:121.16,123.3 1 1 +e-complaint-api/usecases/user/user.go:125.2,126.16 2 1 +e-complaint-api/usecases/user/user.go:126.16,128.3 1 1 +e-complaint-api/usecases/user/user.go:130.2,130.12 1 1 +e-complaint-api/usecases/user/user.go:133.44,135.16 2 1 +e-complaint-api/usecases/user/user.go:135.16,136.48 1 1 +e-complaint-api/usecases/user/user.go:136.48,138.4 1 1 +e-complaint-api/usecases/user/user.go:139.3,139.42 1 1 +e-complaint-api/usecases/user/user.go:142.2,143.16 2 1 +e-complaint-api/usecases/user/user.go:143.16,145.3 1 1 +e-complaint-api/usecases/user/user.go:147.2,147.12 1 1 +e-complaint-api/usecases/user/user.go:150.72,151.23 1 1 +e-complaint-api/usecases/user/user.go:151.23,153.3 1 1 +e-complaint-api/usecases/user/user.go:155.2,155.26 1 1 +e-complaint-api/usecases/user/user.go:155.26,157.3 1 1 +e-complaint-api/usecases/user/user.go:159.2,160.46 2 1 +e-complaint-api/usecases/user/user.go:163.61,164.17 1 1 +e-complaint-api/usecases/user/user.go:164.17,166.3 1 1 +e-complaint-api/usecases/user/user.go:168.2,171.16 3 1 +e-complaint-api/usecases/user/user.go:171.16,173.3 1 1 +e-complaint-api/usecases/user/user.go:175.2,176.16 2 1 +e-complaint-api/usecases/user/user.go:176.16,178.3 1 1 +e-complaint-api/usecases/user/user.go:180.2,180.12 1 1 +e-complaint-api/usecases/user/user.go:183.68,184.30 1 1 +e-complaint-api/usecases/user/user.go:184.30,186.3 1 1 +e-complaint-api/usecases/user/user.go:188.2,188.35 1 1 +e-complaint-api/usecases/user/user.go:188.35,190.17 2 1 +e-complaint-api/usecases/user/user.go:190.17,192.4 1 1 +e-complaint-api/usecases/user/user.go:193.8,195.17 2 1 +e-complaint-api/usecases/user/user.go:195.17,197.4 1 1 +e-complaint-api/usecases/user/user.go:200.2,200.12 1 1 +e-complaint-api/usecases/user/user.go:203.77,204.38 1 1 +e-complaint-api/usecases/user/user.go:204.38,206.3 1 1 +e-complaint-api/usecases/user/user.go:208.2,208.26 1 1 +e-complaint-api/usecases/user/user.go:208.26,210.3 1 1 +e-complaint-api/usecases/user/user.go:212.2,214.16 3 1 +e-complaint-api/usecases/user/user.go:214.16,216.3 1 1 +e-complaint-api/usecases/user/user.go:218.2,218.12 1 1 diff --git a/drivers/mysql/admin/admin.go b/drivers/mysql/admin/admin.go index c4f3b4e..02681c0 100644 --- a/drivers/mysql/admin/admin.go +++ b/drivers/mysql/admin/admin.go @@ -63,7 +63,7 @@ func (r *AdminRepo) GetAdminByID(id int) (*entities.Admin, error) { if errors.Is(result.Error, gorm.ErrRecordNotFound) { return nil, constants.ErrAdminNotFound } - return nil, result.Error + return nil, constants.ErrInternalServerError } return admin, nil } diff --git a/drivers/mysql/category/category.go b/drivers/mysql/category/category.go index 0503a33..b868f82 100644 --- a/drivers/mysql/category/category.go +++ b/drivers/mysql/category/category.go @@ -4,6 +4,7 @@ import ( "e-complaint-api/constants" "e-complaint-api/entities" "errors" + "gorm.io/gorm" ) @@ -56,6 +57,16 @@ func (r *CategoryRepo) UpdateCategory(id int, category *entities.Category) (*ent } func (r *CategoryRepo) DeleteCategory(id int) error { + complaints := r.DB.Where("category_id = ?", id).Find(&entities.Complaint{}) + if complaints.RowsAffected > 0 { + return constants.ErrCategoryHasBeenUsed + } + + news := r.DB.Where("category_id = ?", id).Find(&entities.News{}) + if news.RowsAffected > 0 { + return constants.ErrCategoryHasBeenUsed + } + if err := r.DB.Delete(&entities.Category{}, id).Error; err != nil { return err } diff --git a/drivers/mysql/mysql.go b/drivers/mysql/mysql.go index bb5f38d..ce992d7 100644 --- a/drivers/mysql/mysql.go +++ b/drivers/mysql/mysql.go @@ -73,4 +73,5 @@ func Seeder(db *gorm.DB, regencyAPI entities.RegencyIndonesiaAreaAPIInterface) { seeder.SeedComplaintActivity(db) seeder.SeedFaq(db) seeder.SeedNewsComment(db) + seeder.SeedNewsLike(db) } diff --git a/drivers/mysql/seeder/admin.go b/drivers/mysql/seeder/admin.go index 0e4d0e7..72594b6 100644 --- a/drivers/mysql/seeder/admin.go +++ b/drivers/mysql/seeder/admin.go @@ -10,7 +10,7 @@ import ( func SeedAdmin(db *gorm.DB) { if err := db.First(&entities.Admin{}).Error; errors.Is(err, gorm.ErrRecordNotFound) { - hash, _ := utils.HashPassword("admin") + hash, _ := utils.HashPassword("password") admins := []entities.Admin{ { Name: "Super Admin", diff --git a/drivers/mysql/seeder/complaint.go b/drivers/mysql/seeder/complaint.go index 2ba2c02..04f39b0 100644 --- a/drivers/mysql/seeder/complaint.go +++ b/drivers/mysql/seeder/complaint.go @@ -15,21 +15,21 @@ func SeedComplaint(db *gorm.DB) { ID: "C-81j9aK9280", UserID: 1, CategoryID: 1, - Description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", + Description: "Saya ingin melaporkan kondisi toilet yang sangat kotor di Puskesmas Sentra Kesehatan Masyarakat di wilayah saya. Toilet umum di gedung ini sering kali tidak terawat dengan baik, dinding dan lantai kamar mandi penuh dengan noda dan kotoran, serta bau yang tidak sedap sangat mengganggu penggunaannya. Kondisi ini sangat mengkhawatirkan karena kebersihan toilet yang buruk dapat menyebabkan penyebaran penyakit di antara pengunjung dan staf Puskesmas. Saya berharap pihak terkait segera melakukan perbaikan dan meningkatkan standar kebersihan di fasilitas ini agar dapat memberikan pelayanan kesehatan yang lebih baik dan aman bagi semua orang yang datang ke Puskesmas.", RegencyID: "3601", - Address: "Jl. lorem ipsum No. 1 RT 01 RW 01, Kelurahan Lorem Ipsum, Kecamatan Lorem Ipsum, Kota Lorem Ipsum, Provinsi Lorem Ipsum", + Address: "Jl. Raya Pandeglang No. 123, Desa Cikupa, Kecamatan Labuan, Kabupaten Pandeglang, Provinsi Banten, 42264", Status: "Pending", Type: "public", Date: time.Now(), TotalLikes: 3, }, { - ID: "C-8ksh&s9280", + ID: "C-8kshis9280", UserID: 1, CategoryID: 2, - Description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", + Description: "Saya ingin melaporkan kurangnya fasilitas di SDN 05 Tangerang, yang sangat memprihatinkan. Ruang kelas yang ada saat ini sangat sempit dan tidak cukup untuk menampung jumlah siswa yang terus bertambah. Selain itu, banyak bangku dan meja yang sudah rusak dan belum diganti, sehingga membuat proses belajar mengajar menjadi tidak nyaman. Fasilitas pendukung seperti perpustakaan dan laboratorium juga sangat minim dan kurang terawat. Hal ini sangat mempengaruhi kualitas pendidikan dan kenyamanan belajar siswa. Saya berharap pihak terkait dapat segera memperbaiki dan menambah fasilitas yang dibutuhkan agar proses belajar mengajar dapat berjalan dengan lebih baik.", RegencyID: "3603", - Address: "Jl. lorem ipsum No. 1 RT 01 RW 01, Kelurahan Lorem Ipsum, Kecamatan Lorem Ipsum, Kota Lorem Ipsum, Provinsi Lorem Ipsum", + Address: "Jl. Raya Serpong No. 456, Desa Lengkong Karya, Kecamatan Serpong Utara, Kabupaten Tangerang, Provinsi Banten, 15310", Status: "Selesai", Type: "private", Date: time.Now(), @@ -38,10 +38,10 @@ func SeedComplaint(db *gorm.DB) { { ID: "C-81jas92581", UserID: 2, - CategoryID: 4, - Description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", + CategoryID: 3, + Description: "Saya ingin melaporkan pelayanan yang kurang memadai di Kantor Dinas Kependudukan dan Pencatatan Sipil Kota Serang. Antrian untuk pengurusan KTP, KK, dan akta kelahiran sering kali sangat panjang, dengan waktu tunggu yang tidak menentu dan pelayanan yang lambat. Selain itu, fasilitas ruang tunggu sangat minim dan tidak nyaman, tidak tersedia kursi yang memadai untuk jumlah warga yang datang. Banyak warga yang harus menunggu di luar gedung tanpa perlindungan dari panas dan hujan. Saya berharap pihak terkait dapat meningkatkan efisiensi pelayanan dan memperbaiki fasilitas di kantor tersebut agar warga dapat dilayani dengan lebih cepat dan nyaman.", RegencyID: "3673", - Address: "Jl. lorem ipsum No. 1 RT 01 RW 01, Kelurahan Lorem Ipsum, Kecamatan Lorem Ipsum, Kota Lorem Ipsum, Provinsi Lorem Ipsum", + Address: "Jl. KH. Abdul Hadi No. 89, Kelurahan Lopang, Kecamatan Serang, Kota Serang, Provinsi Banten, 42111.", Status: "Verifikasi", Type: "private", Date: time.Now(), @@ -50,10 +50,10 @@ func SeedComplaint(db *gorm.DB) { { ID: "C-271j9ak280", UserID: 3, - CategoryID: 1, - Description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", + CategoryID: 4, + Description: "Saya ingin melaporkan kondisi keamanan yang memprihatinkan di beberapa wilayah di Kota Tangerang. Belakangan ini, sering terjadi tindak kejahatan seperti pencurian dan perampokan di sekitar Jalan Pahlawan dan sekitarnya. Lampu penerangan jalan yang kurang memadai menjadi salah satu faktor yang mempermudah pelaku kejahatan beraksi pada malam hari. Selain itu, kepolisian setempat terkesan lamban dalam merespons laporan kejahatan dari masyarakat, sehingga membuat warga merasa tidak aman. Saya berharap pihak berwenang dapat meningkatkan patroli keamanan, memperbaiki penerangan jalan, dan meningkatkan respon terhadap laporan kejahatan untuk meningkatkan keamanan dan ketertiban di Kota Tangerang.", RegencyID: "3671", - Address: "Jl. lorem ipsum No. 1 RT 01 RW 01, Kelurahan Lorem Ipsum, Kecamatan Lorem Ipsum, Kota Lorem Ipsum, Provinsi Lorem Ipsum", + Address: "Jl. Gatot Subroto No. 234, Kelurahan Karawaci, Kecamatan Karawaci, Kota Tangerang, Provinsi Banten, 15810", Status: "On Progress", Type: "public", Date: time.Now(), @@ -63,9 +63,9 @@ func SeedComplaint(db *gorm.DB) { ID: "C-123j9ak280", UserID: 3, CategoryID: 6, - Description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", + Description: "Saya ingin melaporkan masalah pencemaran lingkungan yang serius di sekitar kawasan industri di Kota Cilegon. Pabrik-pabrik di sekitar Jalan Raya Merak terus melakukan pembuangan limbah secara ilegal ke sungai yang mengakibatkan sungai tersebut tercemar berat. Air sungai yang sudah tercemar ini sangat berbahaya bagi lingkungan sekitar dan juga kesehatan masyarakat yang menggunakan air dari sungai tersebut. Selain itu, kebisingan dan polusi udara dari aktivitas industri juga telah menciptakan lingkungan yang tidak sehat bagi penduduk sekitar. Saya berharap pemerintah setempat segera mengambil tindakan tegas untuk mengendalikan pembuangan limbah industri, memulihkan kondisi sungai, serta meningkatkan pengawasan terhadap kegiatan industri agar lingkungan Kota Cilegon bisa menjadi lebih bersih dan sehat.", RegencyID: "3672", - Address: "Jl. lorem ipsum No. 1 RT 01 RW 01, Kelurahan Lorem Ipsum, Kecamatan Lorem Ipsum, Kota Lorem Ipsum, Provinsi Lorem Ipsum", + Address: "Jl. Letnan Jenderal Suprapto No. 78, Kelurahan Cibeber, Kecamatan Cilegon, Kota Cilegon, Provinsi Banten, 42441", Status: "Ditolak", Type: "public", Date: time.Now(), diff --git a/drivers/mysql/seeder/complaint_activity.go b/drivers/mysql/seeder/complaint_activity.go index 501bdae..9ae7dd1 100644 --- a/drivers/mysql/seeder/complaint_activity.go +++ b/drivers/mysql/seeder/complaint_activity.go @@ -62,11 +62,11 @@ func SeedComplaintActivity(db *gorm.DB) { LikeID: &LikeID3, }, { - ComplaintID: "C-8ksh&s9280", + ComplaintID: "C-8kshis9280", LikeID: &LikeID4, }, { - ComplaintID: "C-8ksh&s9280", + ComplaintID: "C-8kshis9280", LikeID: &LikeID5, }, { diff --git a/drivers/mysql/seeder/complaint_file.go b/drivers/mysql/seeder/complaint_file.go index 36193b0..3d90649 100644 --- a/drivers/mysql/seeder/complaint_file.go +++ b/drivers/mysql/seeder/complaint_file.go @@ -12,39 +12,43 @@ func SeedComplaintFile(db *gorm.DB) { complaintFiles := []entities.ComplaintFile{ { ComplaintID: "C-81j9aK9280", - Path: "complaint-files/example1.jpg", + Path: "complaint-files/example_kesehatan_1.jpg", }, { ComplaintID: "C-81j9aK9280", - Path: "complaint-files/example2.jpg", + Path: "complaint-files/example_kesehatan_2.jpg", }, { - ComplaintID: "C-8ksh&s9280", - Path: "complaint-files/example3.jpg", + ComplaintID: "C-8kshis9280", + Path: "complaint-files/example_pendidikan_1.jpg", }, { - ComplaintID: "C-8ksh&s9280", - Path: "complaint-files/example3.jpg", + ComplaintID: "C-8kshis9280", + Path: "complaint-files/example_pendidikan_2.jpg", }, { ComplaintID: "C-81jas92581", - Path: "complaint-files/example3.jpg", + Path: "complaint-files/example_kependudukan_1.jpg", }, { ComplaintID: "C-81jas92581", - Path: "complaint-files/example1.jpg", + Path: "complaint-files/example_kependudukan_2.jpg", }, { ComplaintID: "C-271j9ak280", - Path: "complaint-files/example2.jpg", + Path: "complaint-files/example_keamanan_1.jpg", + }, + { + ComplaintID: "C-271j9ak280", + Path: "complaint-files/example_keamanan_2.jpg", }, { ComplaintID: "C-123j9ak280", - Path: "complaint-files/example2.jpg", + Path: "complaint-files/example_lingkungan_1.jpg", }, { ComplaintID: "C-123j9ak280", - Path: "complaint-files/example1.jpg", + Path: "complaint-files/example_lingkungan_2.jpg", }, } diff --git a/drivers/mysql/seeder/complaint_like.go b/drivers/mysql/seeder/complaint_like.go index 202c0d3..76e6d2d 100644 --- a/drivers/mysql/seeder/complaint_like.go +++ b/drivers/mysql/seeder/complaint_like.go @@ -24,11 +24,11 @@ func SeedComplaintLike(db *gorm.DB) { }, { UserID: 2, - ComplaintID: "C-8ksh&s9280", + ComplaintID: "C-8kshis9280", }, { UserID: 3, - ComplaintID: "C-8ksh&s9280", + ComplaintID: "C-8kshis9280", }, { UserID: 1, diff --git a/drivers/mysql/seeder/complaint_process.go b/drivers/mysql/seeder/complaint_process.go index 6ed9d25..df0cb61 100644 --- a/drivers/mysql/seeder/complaint_process.go +++ b/drivers/mysql/seeder/complaint_process.go @@ -17,25 +17,25 @@ func SeedComplaintProcess(db *gorm.DB) { Message: "Aduan anda akan segera kami periksa", }, { - ComplaintID: "C-8ksh&s9280", + ComplaintID: "C-8kshis9280", AdminID: 1, Status: "Pending", Message: "Aduan anda akan segera kami periksa", }, { - ComplaintID: "C-8ksh&s9280", + ComplaintID: "C-8kshis9280", AdminID: 2, Status: "Verifikasi", Message: "Aduan anda telah diverifikasi oleh kami", }, { - ComplaintID: "C-8ksh&s9280", + ComplaintID: "C-8kshis9280", AdminID: 2, Status: "On Progress", Message: "Aduan anda sedang dalam proses penanganan", }, { - ComplaintID: "C-8ksh&s9280", + ComplaintID: "C-8kshis9280", AdminID: 2, Status: "Selesai", Message: "Aduan anda telah selesai ditangani", diff --git a/drivers/mysql/seeder/news.go b/drivers/mysql/seeder/news.go index 235e83a..3e43bb2 100644 --- a/drivers/mysql/seeder/news.go +++ b/drivers/mysql/seeder/news.go @@ -13,20 +13,23 @@ func SeedNews(db *gorm.DB) { { AdminID: 2, CategoryID: 1, - Title: "First News", - Content: "Lorem ipsum dolor sit amet, consectetur adipiscing elit sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", + Title: "Perbaikan Fasilitas Kesehatan di RSUD Tangerang Tanggapi Kritik Masyarakat", + Content: "TANGERANG - Rumah Sakit Umum Daerah (RSUD) Tangerang melakukan langkah signifikan dalam meningkatkan kualitas pelayanan kesehatan dengan memperbaiki fasilitas yang ada. Langkah ini diambil sebagai tanggapan terhadap kritik dari masyarakat terkait kondisi fasilitas yang kurang memadai sebelumnya. \n\nManajer RSUD Tangerang, dr. Kartika Pratiwi, menjelaskan bahwa perbaikan tersebut meliputi renovasi ruang perawatan, peningkatan ketersediaan peralatan medis, dan penambahan tenaga medis untuk memastikan pelayanan yang lebih baik bagi pasien. \"Kami mendengar keluhan masyarakat dan kami berkomitmen untuk memberikan pelayanan yang lebih baik lagi. Renovasi ini adalah langkah awal kami untuk memenuhi standar kesehatan yang lebih tinggi,\" ujar dr. Kartika. \n\nSalah seorang pasien, Bapak Hadi (45), mengapresiasi upaya RSUD Tangerang dalam memperbaiki fasilitas. \"Saya melihat perubahan yang positif di sini. Ruang perawatan lebih bersih dan nyaman, serta pelayanan dari tenaga medis lebih baik,\" katanya. \n\nRSUD Tangerang juga mengundang masukan dari masyarakat untuk terus meningkatkan kualitas pelayanan. \"Kami terbuka untuk kritik dan saran dari masyarakat. Bersama-sama, kami berupaya memberikan pelayanan kesehatan terbaik untuk semua pasien,\" tambah dr. Kartika. \n\nPerbaikan fasilitas di RSUD Tangerang ini diharapkan dapat memberikan dampak positif bagi pelayanan kesehatan di wilayah tersebut dan menjadi contoh untuk institusi kesehatan lainnya dalam memenuhi kebutuhan masyarakat akan layanan kesehatan yang berkualitas.", + TotalLikes: 3, }, { AdminID: 3, CategoryID: 2, - Title: "Second News", - Content: "Lorem ipsum dolor sit amet, consectetur adipiscing elit sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", + Title: "Perbaikan Fasilitas Pendidikan di SMA Negeri 1 Cilegon Menjawab Aspirasi Masyarakat", + Content: "CILEGON - SMA Negeri 1 Cilegon mengumumkan langkah signifikan dalam memperbaiki fasilitas pendidikan demi meningkatkan kualitas belajar mengajar sesuai dengan harapan masyarakat. Langkah ini diambil sebagai respons terhadap kritik dari orang tua siswa dan komunitas pendidikan terkait kondisi fasilitas yang kurang memadai sebelumnya.\n\nKepala Sekolah SMA Negeri 1 Cilegon, Bapak Budi Santoso, menjelaskan bahwa perbaikan tersebut meliputi renovasi ruang kelas, perpustakaan, dan laboratorium, serta peningkatan sarana olahraga untuk mendukung aktivitas siswa di bidang pendidikan dan non-akademik. \"Kami mendengar masukan dari masyarakat dan kami berkomitmen untuk memberikan lingkungan belajar yang lebih baik. Renovasi ini adalah langkah awal kami untuk menciptakan lingkungan pembelajaran yang kondusif dan modern,\" ujar Bapak Budi.\n\nSeorang siswa, Ani (17), mengapresiasi upaya SMA Negeri 1 Cilegon dalam memperbaiki fasilitas. \"Saya merasa senang dengan perubahan ini. Ruang kelas sekarang lebih nyaman dan perpustakaan lebih lengkap,\" katanya.\n\nSMA Negeri 1 Cilegon juga mengundang masukan dari siswa dan orang tua untuk terus meningkatkan kualitas pendidikan. \"Kami terbuka untuk kritik dan saran dari masyarakat pendidikan. Bersama-sama, kami berupaya memberikan pendidikan terbaik untuk semua siswa,\" tambah Bapak Budi.\n\nPerbaikan fasilitas di SMA Negeri 1 Cilegon ini diharapkan dapat memberikan dampak positif bagi proses pendidikan di sekolah tersebut dan menjadi contoh bagi lembaga pendidikan lainnya dalam memenuhi kebutuhan masyarakat akan pendidikan yang berkualitas.", + TotalLikes: 2, }, { AdminID: 2, CategoryID: 4, - Title: "Third News", - Content: "Lorem ipsum dolor sit amet, consectetur adipiscing elit sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", + Title: "Upaya Peningkatan Keamanan di Kota Tangerang Mendapat Dukungan Luas", + Content: "TANGERANG - Pemerintah Kota Tangerang mengumumkan langkah signifikan dalam meningkatkan keamanan di berbagai wilayah, sebagai respons terhadap meningkatnya kekhawatiran masyarakat akan tingkat kejahatan. Langkah ini diambil setelah serangkaian konsultasi dengan warga dan peningkatan patroli polisi di titik-titik rawan.\n\nWali Kota Tangerang, Ibu Siti Nurhayati, menjelaskan bahwa upaya peningkatan keamanan meliputi penambahan jumlah polisi patroli, pemasangan CCTV di titik strategis, dan peningkatan koordinasi antara kepolisian dengan masyarakat. \"Kami mendengar aspirasi warga dan kami bertekad untuk menciptakan lingkungan yang lebih aman. Langkah-langkah ini adalah langkah awal kami untuk meningkatkan rasa aman dan ketertiban,\" ujar Ibu Siti.\n\nSeorang warga, Bapak Adi (40), menyambut baik upaya pemerintah dalam meningkatkan keamanan. \"Saya merasa lebih aman dengan adanya peningkatan patroli dan pemasangan CCTV. Semoga ini dapat membuat lingkungan kami lebih nyaman,\" katanya.\n\nPemerintah Kota Tangerang juga mengundang masukan dari warga untuk terus meningkatkan keamanan. \"Kami terbuka untuk kritik dan saran dari masyarakat. Bersama-sama, kami berupaya menciptakan kota yang lebih aman untuk semua warga,\" tambah Ibu Siti.\n\nPeningkatan keamanan di Kota Tangerang ini diharapkan dapat memberikan dampak positif bagi tingkat kepercayaan masyarakat dan menjadi contoh bagi kota-kota lain dalam memenuhi kebutuhan akan keamanan yang terjamin.", + TotalLikes: 3, }, } diff --git a/drivers/mysql/seeder/news_comment.go b/drivers/mysql/seeder/news_comment.go index f073cb7..ca7f763 100644 --- a/drivers/mysql/seeder/news_comment.go +++ b/drivers/mysql/seeder/news_comment.go @@ -3,6 +3,7 @@ package seeder import ( "e-complaint-api/entities" "errors" + "gorm.io/gorm" ) @@ -10,30 +11,47 @@ func SeedNewsComment(db *gorm.DB) { var newsComment []entities.NewsComment userID1 := 1 - adminID2 := 2 + userID2 := 2 userID3 := 3 + adminID2 := 2 + adminID3 := 3 if err := db.First(&entities.NewsComment{}).Error; errors.Is(err, gorm.ErrRecordNotFound) { newsComment = []entities.NewsComment{ { UserID: &userID1, NewsID: 1, - Comment: "Apa yang terjadi di sana?", + Comment: "Terimakasih atas informasinya", }, { AdminID: &adminID2, NewsID: 1, - Comment: "Sedang terjadi tanah longsor di sana", + Comment: "Sama-sama, semoga bermanfaat", + }, + { + UserID: &userID2, + NewsID: 1, + Comment: "Terimakasih telah memperbaiki fasilitas kesehatan kami", }, { UserID: &userID3, NewsID: 2, - Comment: "Apakah banyak korban jiwa?", + Comment: "Terimakasih telah memperbaiki fasilitas SMA kami", }, { - AdminID: &adminID2, + AdminID: &adminID3, NewsID: 2, - Comment: "Sekitar 10 Orang Sedang di evakuasi", + Comment: "Sama-sama, semoga bermanfaat", + }, + { + UserID: &userID1, + NewsID: 3, + Comment: "Terimakasih telah meningkatkan keamanan di lingkungan kami", + }, + { + AdminID: &adminID3, + NewsID: 3, + Comment: "Sama-sama, semoga lingkungan anda semakin aman", }, } } diff --git a/drivers/mysql/seeder/news_file.go b/drivers/mysql/seeder/news_file.go index 471b07d..05115a2 100644 --- a/drivers/mysql/seeder/news_file.go +++ b/drivers/mysql/seeder/news_file.go @@ -12,23 +12,27 @@ func SeedNewsFile(db *gorm.DB) { newsFiles := []entities.NewsFile{ { NewsID: 1, - Path: "news-files/example1.jpg", + Path: "news-files/example_pendidikan_3.jpg", }, { NewsID: 1, - Path: "news-files/example2.jpg", + Path: "news-files/example_pendidikan_4.jpg", }, { NewsID: 2, - Path: "news-files/example3.jpg", + Path: "news-files/example_kesehatan_1.jpg", + }, + { + NewsID: 2, + Path: "news-files/example_kesehatan_2.jpg", }, { NewsID: 3, - Path: "news-files/example1.jpg", + Path: "news-files/example_keamanan_5.jpg", }, { NewsID: 3, - Path: "news-files/example3.jpg", + Path: "news-files/example_keamanan_6.jpg", }, } diff --git a/drivers/mysql/seeder/news_like.go b/drivers/mysql/seeder/news_like.go new file mode 100644 index 0000000..de4c9b4 --- /dev/null +++ b/drivers/mysql/seeder/news_like.go @@ -0,0 +1,51 @@ +package seeder + +import ( + "e-complaint-api/entities" + "errors" + + "gorm.io/gorm" +) + +func SeedNewsLike(db *gorm.DB) { + if err := db.First(&entities.NewsLike{}).Error; errors.Is(err, gorm.ErrRecordNotFound) { + newsLikes := []entities.NewsLike{ + { + NewsID: 1, + UserID: 1, + }, + { + NewsID: 1, + UserID: 2, + }, + { + NewsID: 1, + UserID: 3, + }, + { + NewsID: 2, + UserID: 1, + }, + { + NewsID: 2, + UserID: 2, + }, + { + NewsID: 3, + UserID: 1, + }, + { + NewsID: 3, + UserID: 2, + }, + { + NewsID: 3, + UserID: 3, + }, + } + + if err := db.CreateInBatches(newsLikes, len(newsLikes)).Error; err != nil { + panic(err) + } + } +} diff --git a/drivers/mysql/seeder/user.go b/drivers/mysql/seeder/user.go index ac417a8..65e8478 100644 --- a/drivers/mysql/seeder/user.go +++ b/drivers/mysql/seeder/user.go @@ -10,28 +10,31 @@ import ( func SeedUser(db *gorm.DB) { if err := db.First(&entities.User{}).Error; errors.Is(err, gorm.ErrRecordNotFound) { - hash, _ := utils.HashPassword("user") + hash, _ := utils.HashPassword("password") users := []entities.User{ { - Name: "User 1", + Name: "Putra Ramadhan", Password: hash, - Email: "user1@gmail.com", + Email: "putraramadhan@gmail.com", TelephoneNumber: "081234567890", EmailVerified: true, + ProfilePhoto: "profile-photos/example_1.jpg", }, { - Name: "User 2", + Name: "Andika Saputra", Password: hash, - Email: "user2@gmail.com", + Email: "andikaputra@gmail.com", TelephoneNumber: "081234567890", EmailVerified: true, + ProfilePhoto: "profile-photos/example_2.jpg", }, { - Name: "User 3", + Name: "Muhammad Iqbal", Password: hash, - Email: "user3@gmail.com", + Email: "muhammadiqbal@gmail.com", TelephoneNumber: "081234567890", EmailVerified: true, + ProfilePhoto: "profile-photos/example_3.jpg", }, } diff --git a/drivers/mysql/user/user.go b/drivers/mysql/user/user.go index e924f81..2ea4b31 100644 --- a/drivers/mysql/user/user.go +++ b/drivers/mysql/user/user.go @@ -66,7 +66,7 @@ func (r *UserRepo) GetUserByID(id int) (*entities.User, error) { if err := r.DB.First(&user, id).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { - return nil, constants.ErrNotFound + return nil, constants.ErrUserNotFound } return nil, err } @@ -107,7 +107,7 @@ func (r *UserRepo) SendOTP(email, otp string) error { var user entities.User if err := r.DB.Model(&entities.User{}).Where("email = ?", email).First(&user).Error; err != nil { - return constants.ErrUserNotFound + return constants.ErrEmailNotRegistered } user.Otp = otp @@ -123,7 +123,7 @@ func (r *UserRepo) SendOTP(email, otp string) error { func (r *UserRepo) VerifyOTPRegister(email, otp string) error { var user entities.User if err := r.DB.Model(&entities.User{}).Where("email = ?", email).First(&user).Error; err != nil { - return constants.ErrUserNotFound + return constants.ErrEmailNotRegistered } if user.Otp != otp { diff --git a/entities/admin.go b/entities/admin.go index dd42fc3..18359a5 100644 --- a/entities/admin.go +++ b/entities/admin.go @@ -7,13 +7,13 @@ import ( ) type Admin struct { - ID int `gorm:"primaryKey"` - Name string `gorm:"not null"` - Email string `gorm:"unique"` - Password string `gorm:"not null"` - TelephoneNumber string + ID int `gorm:"primaryKey"` + Name string `gorm:"not null;type:varchar(255)"` + Email string `gorm:"unique;type:varchar(255)"` + Password string `gorm:"not null;type:varchar(255)"` + TelephoneNumber string `gorm:"type:varchar(20)"` IsSuperAdmin bool `gorm:"default:false"` - ProfilePhoto string `gorm:"default:profile-photos/admin-default.jpg"` + ProfilePhoto string `gorm:"default:profile-photos/admin-default.jpg;type:varchar(255)"` CreatedAt time.Time `gorm:"autoCreateTime"` UpdatedAt time.Time `gorm:"autoUpdateTime"` DeletedAt gorm.DeletedAt `gorm:"index"` diff --git a/entities/category.go b/entities/category.go index 86d57a4..e3829cc 100644 --- a/entities/category.go +++ b/entities/category.go @@ -1,9 +1,18 @@ package entities +import ( + "time" + + "gorm.io/gorm" +) + type Category struct { - ID int `gorm:"primaryKey"` - Name string `gorm:"not null"` - Description string `gorm:"not null"` + ID int `gorm:"primaryKey"` + Name string `gorm:"not null;type:varchar(255)"` + Description string `gorm:"not null"` + CreatedAt time.Time `gorm:"autoCreateTime"` + UpdatedAt time.Time `gorm:"autoUpdateTime"` + DeletedAt gorm.DeletedAt `gorm:"index"` } type CategoryRepositoryInterface interface { diff --git a/entities/complaint.go b/entities/complaint.go index 09fb26d..0d4067d 100644 --- a/entities/complaint.go +++ b/entities/complaint.go @@ -8,12 +8,12 @@ import ( ) type Complaint struct { - ID string `gorm:"primaryKey;length:15"` + ID string `gorm:"primaryKey;type:varchar;size:15;"` UserID int `gorm:"not null"` CategoryID int `gorm:"not null"` - Description string `gorm:"not null"` - RegencyID string `gorm:"not null;type:varchar;size:4"` + RegencyID string `gorm:"not null;type:varchar;size:4;"` Address string `gorm:"not null"` + Description string `gorm:"not null"` Status string `gorm:"type:enum('Pending', 'Verifikasi', 'On Progress', 'Selesai', 'Ditolak');default:'Pending'"` Type string `gorm:"type:enum('public', 'private')"` Date time.Time `gorm:"type:date"` diff --git a/entities/complaint_file.go b/entities/complaint_file.go index 5285f6d..ca1b0b5 100644 --- a/entities/complaint_file.go +++ b/entities/complaint_file.go @@ -10,7 +10,7 @@ import ( type ComplaintFile struct { ID int `gorm:"primaryKey"` ComplaintID string `gorm:"not null;type:varchar;size:15;"` - Path string `gorm:"not null"` + Path string `gorm:"not null;type:varchar(255)"` CreatedAt time.Time `gorm:"autoCreateTime"` UpdatedAt time.Time `gorm:"autoUpdateTime"` DeletedAt gorm.DeletedAt `gorm:"index"` diff --git a/entities/complaint_like.go b/entities/complaint_like.go index 3655d67..d747021 100644 --- a/entities/complaint_like.go +++ b/entities/complaint_like.go @@ -4,8 +4,8 @@ import "time" type ComplaintLike struct { ID int `gorm:"primaryKey"` - ComplaintID string `gorm:"type:varchar;size:15;not null"` UserID int `gorm:"not null"` + ComplaintID string `gorm:"type:varchar;size:15;not null"` CreatedAt time.Time `gorm:"autoCreateTime"` Complaint Complaint `gorm:"foreignKey:ComplaintID;references:ID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE"` User User `gorm:"foreignKey:UserID;references:ID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE"` diff --git a/entities/discussion.go b/entities/discussion.go index f58272b..2afe306 100644 --- a/entities/discussion.go +++ b/entities/discussion.go @@ -8,10 +8,10 @@ import ( type Discussion struct { ID int `gorm:"primaryKey;autoIncrement"` - Comment string `gorm:"not null"` UserID *int `gorm:"index;constraint:OnUpdate:CASCADE,OnDelete:SET NULL"` AdminID *int `gorm:"index;constraint:OnUpdate:CASCADE,OnDelete:SET NULL"` ComplaintID string `gorm:"type:varchar(15);index;constraint:OnUpdate:CASCADE,OnDelete:SET NULL"` + Comment string `gorm:"not null;type:text"` User User `gorm:"foreignKey:UserID;references:ID"` Admin Admin `gorm:"foreignKey:AdminID;references:ID"` Complaint Complaint `gorm:"foreignKey:ComplaintID;references:ID"` diff --git a/entities/faq.go b/entities/faq.go index 5c7d07a..95fd6cc 100644 --- a/entities/faq.go +++ b/entities/faq.go @@ -2,8 +2,8 @@ package entities type Faq struct { ID int `gorm:"primaryKey"` - Question string `gorm:"not null"` - Answer string `gorm:"not null"` + Question string `gorm:"not null;type:text"` + Answer string `gorm:"not null;type:text"` } type FaqRepositoryInterface interface { diff --git a/entities/news.go b/entities/news.go index 6656ca1..e7d7fa8 100644 --- a/entities/news.go +++ b/entities/news.go @@ -10,7 +10,7 @@ type News struct { ID int `gorm:"primaryKey"` AdminID int `gorm:"not null"` CategoryID int `gorm:"not null"` - Title string `gorm:"not null"` + Title string `gorm:"not null;type:varchar(255)"` Content string `gorm:"not null"` TotalLikes int `gorm:"default:0"` CreatedAt time.Time `gorm:"autoCreateTime"` diff --git a/entities/news_file.go b/entities/news_file.go index d3a54ad..55a2a57 100644 --- a/entities/news_file.go +++ b/entities/news_file.go @@ -10,7 +10,7 @@ import ( type NewsFile struct { ID int `gorm:"primaryKey"` NewsID int `gorm:"not null"` - Path string `gorm:"not null"` + Path string `gorm:"not null;type:varchar(255)"` CreatedAt time.Time `gorm:"autoCreateTime"` UpdatedAt time.Time `gorm:"autoUpdateTime"` DeletedAt gorm.DeletedAt `gorm:"index"` diff --git a/entities/regency.go b/entities/regency.go index 3466432..63d3205 100644 --- a/entities/regency.go +++ b/entities/regency.go @@ -2,7 +2,7 @@ package entities type Regency struct { ID string `gorm:"primaryKey"` - Name string `gorm:"not null"` + Name string `gorm:"not null;type:varchar(255)"` } type RegencyRepositoryInterface interface { diff --git a/entities/user.go b/entities/user.go index 0c5b519..c22bfed 100644 --- a/entities/user.go +++ b/entities/user.go @@ -8,22 +8,22 @@ import ( ) type User struct { - ID int `gorm:"primaryKey"` - Name string `gorm:"not null"` - Email string `gorm:"unique"` - Password string `gorm:"not null"` - TelephoneNumber string - ProfilePhoto string `gorm:"default:profile-photos/default.jpg"` + ID int `gorm:"primaryKey"` + Name string `gorm:"not null;type:varchar(255)"` + Email string `gorm:"unique;not null;type:varchar(255)"` + Password string `gorm:"not null;type:varchar(255)"` + TelephoneNumber string `gorm:"not null;type:varchar(20)"` + ProfilePhoto string `gorm:"default:profile-photos/default.jpg;type:varchar(255)"` Token string `gorm:"-"` + Otp string `gorm:"default:null;type:varchar(5)"` + OtpExpiredAt time.Time `gorm:"default:null"` + EmailVerified bool `gorm:"default:false"` + ForgotVerified bool `gorm:"default:false"` Discussion []Discussion `gorm:"foreignKey:UserID;references:ID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"` NewsComment []NewsComment `gorm:"foreignKey:UserID;references:ID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"` CreatedAt time.Time `gorm:"autoCreateTime"` UpdatedAt time.Time `gorm:"autoUpdateTime"` DeletedAt gorm.DeletedAt `gorm:"index"` - Otp string `gorm:"default:null"` - OtpExpiredAt time.Time `gorm:"default:null"` - EmailVerified bool `gorm:"default:false"` - ForgotVerified bool `gorm:"default:false"` } type UserRepositoryInterface interface { @@ -57,7 +57,7 @@ type UserUseCaseInterface interface { UpdateUser(id int, user *User) (User, error) UpdateProfilePhoto(id int, profilePhoto *multipart.FileHeader) error Delete(id int) error - UpdatePassword(id int, newPassword, confirmNewPassword string) error + UpdatePassword(id int, newPassword string) error SendOTP(email, otp_type string) error VerifyOTP(email, otp, otp_type string) error UpdatePasswordForgot(email, newPassword string) error diff --git a/go.mod b/go.mod index 43a8e26..17bcd6c 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/labstack/echo-jwt v0.0.0-20221127215225-c84d41a71003 github.com/labstack/echo/v4 v4.12.0 github.com/sashabaranov/go-openai v1.24.1 + github.com/stretchr/testify v1.9.0 github.com/xuri/excelize/v2 v2.8.1 golang.org/x/crypto v0.23.0 google.golang.org/api v0.181.0 @@ -23,6 +24,7 @@ require ( cloud.google.com/go/auth/oauth2adapt v0.2.2 // indirect cloud.google.com/go/compute/metadata v0.3.0 // indirect cloud.google.com/go/iam v1.1.8 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect @@ -41,8 +43,10 @@ require ( github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/richardlehane/mscfb v1.0.4 // indirect github.com/richardlehane/msoleps v1.0.3 // indirect + github.com/stretchr/objx v0.5.2 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect github.com/xuri/efp v0.0.0-20231025114914-d1ff6096ae53 // indirect @@ -65,4 +69,5 @@ require ( google.golang.org/grpc v1.63.2 // indirect google.golang.org/protobuf v1.34.1 // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 7b3622b..6bca702 100644 --- a/go.sum +++ b/go.sum @@ -104,6 +104,8 @@ github.com/sashabaranov/go-openai v1.24.1/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adO github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= @@ -214,6 +216,7 @@ google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFW google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE= gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw= diff --git a/main.go b/main.go index 8169b0a..66d0e57 100644 --- a/main.go +++ b/main.go @@ -159,7 +159,7 @@ func main() { NewsCommentController := news_comment.NewNewsCommentController(newsCommentUsecase, newsUsecase) dashboardRepo := dashboard_repo.NewDashboardRepo(DB) - dashboardUsecase := dashboard_uc.NewDashboardUseCase(*dashboardRepo) + dashboardUsecase := dashboard_uc.NewDashboardUseCase(dashboardRepo) dashboardController := dashboard_cl.NewDashboardController(*dashboardUsecase) routes := routes.RouteController{ diff --git a/usecases/admin/admin.go b/usecases/admin/admin.go index 1f92e39..b02a358 100644 --- a/usecases/admin/admin.go +++ b/usecases/admin/admin.go @@ -23,6 +23,10 @@ func (u *AdminUseCase) CreateAccount(admin *entities.Admin) (entities.Admin, err return entities.Admin{}, constants.ErrAllFieldsMustBeFilled } + if len(admin.Password) < 8 { + return entities.Admin{}, constants.ErrPasswordMustBeAtLeast8Characters + } + err := u.repository.CreateAccount(admin) if err != nil { @@ -74,11 +78,7 @@ func (u *AdminUseCase) GetAllAdmins() ([]entities.Admin, error) { func (u *AdminUseCase) GetAdminByID(id int) (*entities.Admin, error) { admin, err := u.repository.GetAdminByID(id) if admin == nil { - return nil, constants.ErrAdminNotFound - } - - if err != nil { - return nil, constants.ErrInternalServerError + return nil, err } return admin, nil @@ -108,10 +108,6 @@ func (u *AdminUseCase) UpdateAdmin(id int, admin *entities.Admin) (entities.Admi return entities.Admin{}, constants.ErrInternalServerError } - if admin == nil { - return entities.Admin{}, constants.ErrAdminNotFound - } - // Check if the email is already taken by another admin if admin.Email != "" && admin.Email != existingAdmin.Email { conflictingAdmin, err := u.repository.GetAdminByEmail(admin.Email) @@ -144,6 +140,10 @@ func (u *AdminUseCase) UpdateAdmin(id int, admin *entities.Admin) (entities.Admi return *existingAdmin, constants.ErrNoChangesDetected } + if len(admin.Password) < 8 { + return entities.Admin{}, constants.ErrPasswordMustBeAtLeast8Characters + } + err = u.repository.UpdateAdmin(id, existingAdmin) if err != nil { return entities.Admin{}, constants.ErrInternalServerError diff --git a/usecases/admin/admin_test.go b/usecases/admin/admin_test.go new file mode 100644 index 0000000..bd442a8 --- /dev/null +++ b/usecases/admin/admin_test.go @@ -0,0 +1,588 @@ +package admin + +import ( + "e-complaint-api/constants" + "e-complaint-api/entities" + "errors" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +type MockAdminRepository struct { + mock.Mock +} + +func (m *MockAdminRepository) CreateAccount(admin *entities.Admin) error { + args := m.Called(admin) + return args.Error(0) +} + +func (m *MockAdminRepository) Login(admin *entities.Admin) error { + args := m.Called(admin) + return args.Error(0) +} + +func (m *MockAdminRepository) GetAllAdmins() ([]*entities.Admin, error) { + args := m.Called() + return args.Get(0).([]*entities.Admin), args.Error(1) +} + +func (m *MockAdminRepository) GetAdminByID(id int) (*entities.Admin, error) { + args := m.Called(id) + return args.Get(0).(*entities.Admin), args.Error(1) +} + +func (m *MockAdminRepository) DeleteAdmin(id int) error { + args := m.Called(id) + return args.Error(0) +} + +func (m *MockAdminRepository) UpdateAdmin(id int, admin *entities.Admin) error { + args := m.Called(id, admin) + return args.Error(0) +} + +func (m *MockAdminRepository) GetAdminByEmail(email string) (*entities.Admin, error) { + args := m.Called(email) + return args.Get(0).(*entities.Admin), args.Error(1) +} + +func TestCreateAccount(t *testing.T) { + t.Run("success", func(t *testing.T) { + mockAdminRepo := new(MockAdminRepository) + AdminUseCase := NewAdminUseCase(mockAdminRepo) + + admin := entities.Admin{ + Name: "admin", + Email: "admin@gmail.com", + Password: "admin12345", + TelephoneNumber: "08123456789", + } + + mockAdminRepo.On("CreateAccount", &admin).Return(nil) + + result, err := AdminUseCase.CreateAccount(&admin) + assert.NoError(t, err) + assert.Equal(t, admin, result) + + mockAdminRepo.AssertExpectations(t) + }) + + t.Run("failed empty field", func(t *testing.T) { + mockAdminRepo := new(MockAdminRepository) + AdminUseCase := NewAdminUseCase(mockAdminRepo) + + admin := entities.Admin{ + Name: "", + Email: "admin@gmail.com", + Password: "admin12345", + TelephoneNumber: "08123456789", + } + + result, err := AdminUseCase.CreateAccount(&admin) + assert.Error(t, constants.ErrAllFieldsMustBeFilled, err) + assert.Equal(t, entities.Admin{}, result) + + mockAdminRepo.AssertExpectations(t) + }) + + t.Run("failed email already exists", func(t *testing.T) { + mockAdminRepo := new(MockAdminRepository) + AdminUseCase := NewAdminUseCase(mockAdminRepo) + + admin := entities.Admin{ + Name: "admin", + Email: "admin@gmail.com", + Password: "admin12345", + TelephoneNumber: "08123456789", + } + + mockAdminRepo.On("CreateAccount", &admin).Return(errors.New("Error 1062: Duplicate entry 'admin' for key 'email'")) + + result, err := AdminUseCase.CreateAccount(&admin) + assert.Error(t, constants.ErrEmailAlreadyExists, err) + assert.Equal(t, entities.Admin{}, result) + + mockAdminRepo.AssertExpectations(t) + }) + + t.Run("failed internal server error", func(t *testing.T) { + mockAdminRepo := new(MockAdminRepository) + AdminUseCase := NewAdminUseCase(mockAdminRepo) + + admin := entities.Admin{ + Name: "admin", + Email: "admin@gmail.com", + Password: "admin12345", + TelephoneNumber: "08123456789", + } + + mockAdminRepo.On("CreateAccount", &admin).Return(constants.ErrInternalServerError) + + result, err := AdminUseCase.CreateAccount(&admin) + assert.Error(t, constants.ErrInternalServerError, err) + assert.Equal(t, entities.Admin{}, result) + + mockAdminRepo.AssertExpectations(t) + }) + + t.Run("failed username already exists", func(t *testing.T) { + mockAdminRepo := new(MockAdminRepository) + AdminUseCase := NewAdminUseCase(mockAdminRepo) + + admin := entities.Admin{ + Name: "admin", + Email: "admin@gmail.com", + Password: "admin12345", + TelephoneNumber: "08123456789", + } + + mockAdminRepo.On("CreateAccount", &admin).Return(errors.New("Error 1062: Duplicate entry 'admin' for key 'username'")) + + result, err := AdminUseCase.CreateAccount(&admin) + assert.Error(t, constants.ErrUsernameAlreadyExists, err) + assert.Equal(t, entities.Admin{}, result) + + mockAdminRepo.AssertExpectations(t) + }) + + t.Run("failed password must be at least 8 characters", func(t *testing.T) { + mockAdminRepo := new(MockAdminRepository) + AdminUseCase := NewAdminUseCase(mockAdminRepo) + + admin := entities.Admin{ + Name: "admin", + Email: "admin@gmail.com", + Password: "admin", + TelephoneNumber: "08123456789", + } + + result, err := AdminUseCase.CreateAccount(&admin) + assert.Error(t, constants.ErrPasswordMustBeAtLeast8Characters, err) + assert.Equal(t, entities.Admin{}, result) + + mockAdminRepo.AssertExpectations(t) + }) +} + +func TestLogin(t *testing.T) { + t.Run("success admin", func(t *testing.T) { + mockAdminRepo := new(MockAdminRepository) + AdminUseCase := NewAdminUseCase(mockAdminRepo) + + admin := entities.Admin{ + Email: "admin@gmail.com", + Password: "admin", + } + + mockAdminRepo.On("Login", &admin).Return(nil) + + result, err := AdminUseCase.Login(&admin) + assert.NoError(t, err) + assert.Equal(t, admin, result) + + mockAdminRepo.AssertExpectations(t) + }) + + t.Run("success super admin", func(t *testing.T) { + mockAdminRepo := new(MockAdminRepository) + AdminUseCase := NewAdminUseCase(mockAdminRepo) + + admin := entities.Admin{ + Email: "super_admin@gmail.com", + Password: "super_admin", + IsSuperAdmin: true, + } + + mockAdminRepo.On("Login", &admin).Return(nil) + + result, err := AdminUseCase.Login(&admin) + assert.NoError(t, err) + assert.Equal(t, admin, result) + + mockAdminRepo.AssertExpectations(t) + }) + + t.Run("failed empty field", func(t *testing.T) { + mockAdminRepo := new(MockAdminRepository) + AdminUseCase := NewAdminUseCase(mockAdminRepo) + + admin := entities.Admin{ + Email: "", + Password: "admin", + } + + result, err := AdminUseCase.Login(&admin) + assert.Error(t, constants.ErrAllFieldsMustBeFilled, err) + assert.Equal(t, entities.Admin{}, result) + + mockAdminRepo.AssertExpectations(t) + }) + + t.Run("failed invalid username or password", func(t *testing.T) { + mockAdminRepo := new(MockAdminRepository) + AdminUseCase := NewAdminUseCase(mockAdminRepo) + + admin := entities.Admin{ + Email: "admin@gmail.com", + Password: "admin", + } + + mockAdminRepo.On("Login", &admin).Return(constants.ErrInvalidUsernameOrPassword) + + result, err := AdminUseCase.Login(&admin) + assert.Error(t, constants.ErrInvalidUsernameOrPassword, err) + assert.Equal(t, entities.Admin{}, result) + + mockAdminRepo.AssertExpectations(t) + }) +} + +func TestGetAllAdmins(t *testing.T) { + t.Run("success", func(t *testing.T) { + mockAdminRepo := new(MockAdminRepository) + AdminUseCase := NewAdminUseCase(mockAdminRepo) + + admins := []*entities.Admin{ + { + Name: "admin1", + Email: "admin1@gmail.com", + Password: "admin1", + TelephoneNumber: "08123456789", + }, + { + Name: "admin2", + Email: "admin2@gmail.com", + Password: "admin2", + TelephoneNumber: "08123456789", + }, + } + + adminValues := make([]entities.Admin, len(admins)) + for i, admin := range admins { + adminValues[i] = *admin + } + + mockAdminRepo.On("GetAllAdmins").Return(admins, nil) + + result, err := AdminUseCase.GetAllAdmins() + assert.NoError(t, err) + assert.Equal(t, adminValues, result) + + mockAdminRepo.AssertExpectations(t) + }) + + t.Run("failed", func(t *testing.T) { + mockAdminRepo := new(MockAdminRepository) + AdminUseCase := NewAdminUseCase(mockAdminRepo) + + mockAdminRepo.On("GetAllAdmins").Return(([]*entities.Admin)(nil), constants.ErrInternalServerError) + + result, err := AdminUseCase.GetAllAdmins() + assert.Error(t, constants.ErrInternalServerError, err) + assert.Nil(t, result) + + mockAdminRepo.AssertExpectations(t) + }) +} + +func TestGetAdminByID(t *testing.T) { + t.Run("success", func(t *testing.T) { + mockAdminRepo := new(MockAdminRepository) + AdminUseCase := NewAdminUseCase(mockAdminRepo) + + admin := entities.Admin{ + ID: 1, + Name: "admin", + Email: "admin@gmail.com", + Password: "admin12345", + TelephoneNumber: "08123456789", + } + + mockAdminRepo.On("GetAdminByID", 1).Return(&admin, nil) + + result, err := AdminUseCase.GetAdminByID(1) + assert.NoError(t, err) + assert.Equal(t, &admin, result) + + mockAdminRepo.AssertExpectations(t) + }) + + t.Run("failed admin not found", func(t *testing.T) { + mockAdminRepo := new(MockAdminRepository) + AdminUseCase := NewAdminUseCase(mockAdminRepo) + + mockAdminRepo.On("GetAdminByID", 1).Return((*entities.Admin)(nil), constants.ErrAdminNotFound) + + result, err := AdminUseCase.GetAdminByID(1) + assert.Error(t, constants.ErrAdminNotFound, err) + assert.Nil(t, result) + + mockAdminRepo.AssertExpectations(t) + }) + + t.Run("failed internal server error", func(t *testing.T) { + mockAdminRepo := new(MockAdminRepository) + AdminUseCase := NewAdminUseCase(mockAdminRepo) + + mockAdminRepo.On("GetAdminByID", 1).Return((*entities.Admin)(nil), constants.ErrInternalServerError) + + result, err := AdminUseCase.GetAdminByID(1) + assert.Error(t, constants.ErrInternalServerError, err) + assert.Nil(t, result) + + mockAdminRepo.AssertExpectations(t) + }) +} + +func TestDeleteAdmin(t *testing.T) { + t.Run("success", func(t *testing.T) { + mockAdminRepo := new(MockAdminRepository) + AdminUseCase := NewAdminUseCase(mockAdminRepo) + + mockAdminRepo.On("GetAdminByID", 1).Return(&entities.Admin{}, nil) + mockAdminRepo.On("DeleteAdmin", 1).Return(nil) + + err := AdminUseCase.DeleteAdmin(1) + assert.NoError(t, err) + + mockAdminRepo.AssertExpectations(t) + }) + + t.Run("failed admin not found", func(t *testing.T) { + mockAdminRepo := new(MockAdminRepository) + AdminUseCase := NewAdminUseCase(mockAdminRepo) + + mockAdminRepo.On("GetAdminByID", 1).Return((*entities.Admin)(nil), constants.ErrAdminNotFound) + + err := AdminUseCase.DeleteAdmin(1) + assert.Error(t, constants.ErrAdminNotFound, err) + + mockAdminRepo.AssertExpectations(t) + }) + + t.Run("failed internal server error", func(t *testing.T) { + mockAdminRepo := new(MockAdminRepository) + AdminUseCase := NewAdminUseCase(mockAdminRepo) + + mockAdminRepo.On("GetAdminByID", 1).Return(&entities.Admin{}, nil) + mockAdminRepo.On("DeleteAdmin", 1).Return(constants.ErrInternalServerError) + + err := AdminUseCase.DeleteAdmin(1) + assert.Error(t, constants.ErrInternalServerError, err) + + mockAdminRepo.AssertExpectations(t) + }) +} + +func TestUpdateAdmin(t *testing.T) { + t.Run("success", func(t *testing.T) { + mockAdminRepo := new(MockAdminRepository) + AdminUseCase := NewAdminUseCase(mockAdminRepo) + + admin := entities.Admin{ + ID: 1, + Name: "admin", + Email: "admin@gmail.com", + Password: "admin12345", + TelephoneNumber: "08123456789", + } + + updatedAdmin := entities.Admin{ + ID: 1, + Name: "updated_admin", + Email: "updated_admin@gmail.com", + Password: "updated_admin", + TelephoneNumber: "08123456780", + } + + mockAdminRepo.On("GetAdminByID", 1).Return(&admin, nil) + mockAdminRepo.On("GetAdminByEmail", updatedAdmin.Email).Return((*entities.Admin)(nil), nil) + mockAdminRepo.On("UpdateAdmin", 1, &updatedAdmin).Return(nil) + + result, err := AdminUseCase.UpdateAdmin(1, &updatedAdmin) + assert.NoError(t, err) + assert.Equal(t, updatedAdmin, result) + + mockAdminRepo.AssertExpectations(t) + }) + + t.Run("failed admin not found", func(t *testing.T) { + mockAdminRepo := new(MockAdminRepository) + AdminUseCase := NewAdminUseCase(mockAdminRepo) + + updatedAdmin := entities.Admin{ + ID: 1, + Name: "updated_admin", + Email: "admin@gmail.com", + Password: "updated_admin", + TelephoneNumber: "08123456780", + } + + mockAdminRepo.On("GetAdminByID", 1).Return((*entities.Admin)(nil), constants.ErrAdminNotFound) + + result, err := AdminUseCase.UpdateAdmin(1, &updatedAdmin) + assert.Error(t, constants.ErrAdminNotFound, err) + assert.Equal(t, entities.Admin{}, result) + + mockAdminRepo.AssertExpectations(t) + }) + + t.Run("failed internal server error when getting admin by email", func(t *testing.T) { + mockAdminRepo := new(MockAdminRepository) + AdminUseCase := NewAdminUseCase(mockAdminRepo) + + admin := entities.Admin{ + ID: 1, + Name: "admin", + Email: "admin@gmail.com", + Password: "admin12345", + TelephoneNumber: "08123456789", + } + + updatedAdmin := entities.Admin{ + ID: 1, + Name: "updated_admin", + Email: "admin@gmail.com", + Password: "updated_admin", + TelephoneNumber: "08123456780", + } + + mockAdminRepo.On("GetAdminByID", 1).Return(&admin, constants.ErrInternalServerError) + + result, err := AdminUseCase.UpdateAdmin(1, &updatedAdmin) + assert.Error(t, constants.ErrInternalServerError, err) + assert.Equal(t, entities.Admin{}, result) + + mockAdminRepo.AssertExpectations(t) + }) + + t.Run("failed email already exists", func(t *testing.T) { + mockAdminRepo := new(MockAdminRepository) + AdminUseCase := NewAdminUseCase(mockAdminRepo) + + admin := entities.Admin{ + ID: 1, + Name: "admin", + Email: "admin123@gmail.com", + Password: "admin12345", + TelephoneNumber: "08123456789", + } + + updatedAdmin := entities.Admin{ + ID: 1, + Name: "updated_admin", + Email: "admin@gmail.com", + Password: "updated_admin", + TelephoneNumber: "08123456780", + } + + conflictingAdmin := entities.Admin{ + ID: 2, + Name: "other_admin", + Email: "admin@gmail.com", + Password: "password", + TelephoneNumber: "08123456780", + } + + mockAdminRepo.On("GetAdminByID", 1).Return(&admin, nil) + mockAdminRepo.On("GetAdminByEmail", updatedAdmin.Email).Return(&conflictingAdmin, nil) + + result, err := AdminUseCase.UpdateAdmin(1, &updatedAdmin) + assert.Equal(t, constants.ErrEmailAlreadyExists, err) + assert.Equal(t, entities.Admin{}, result) + + mockAdminRepo.AssertExpectations(t) + }) + + t.Run("failed no new data provided", func(t *testing.T) { + mockAdminRepo := new(MockAdminRepository) + AdminUseCase := NewAdminUseCase(mockAdminRepo) + + admin := entities.Admin{ + ID: 1, + Name: "admin", + Email: "admin@gmail.com", + Password: "admin12345", + TelephoneNumber: "08123456789", + } + + updatedAdmin := entities.Admin{ + ID: 0, + Name: "", + Email: "", + Password: "", + TelephoneNumber: "", + } + + mockAdminRepo.On("GetAdminByID", 1).Return(&admin, nil) + + result, err := AdminUseCase.UpdateAdmin(1, &updatedAdmin) + assert.Equal(t, constants.ErrNoChangesDetected, err) + assert.Equal(t, admin, result) + + mockAdminRepo.AssertExpectations(t) + }) + + t.Run("failed internal server error when updating admin", func(t *testing.T) { + mockAdminRepo := new(MockAdminRepository) + AdminUseCase := NewAdminUseCase(mockAdminRepo) + + admin := entities.Admin{ + ID: 1, + Name: "admin", + Email: "admin@gmail.com", + Password: "admin12345", + TelephoneNumber: "08123456789", + } + + updatedAdmin := entities.Admin{ + ID: 1, + Name: "updated_admin", + Email: "updated_admin@gmail.com", + Password: "updated_admin", + TelephoneNumber: "08123456780", + } + + mockAdminRepo.On("GetAdminByID", 1).Return(&admin, nil) + mockAdminRepo.On("GetAdminByEmail", updatedAdmin.Email).Return((*entities.Admin)(nil), nil) + mockAdminRepo.On("UpdateAdmin", 1, &updatedAdmin).Return(constants.ErrInternalServerError) + + result, err := AdminUseCase.UpdateAdmin(1, &updatedAdmin) + assert.Error(t, constants.ErrInternalServerError, err) + assert.Equal(t, entities.Admin{}, result) + + mockAdminRepo.AssertExpectations(t) + }) + + t.Run("failed password must be at least 8 characters", func(t *testing.T) { + mockAdminRepo := new(MockAdminRepository) + AdminUseCase := NewAdminUseCase(mockAdminRepo) + + admin := entities.Admin{ + ID: 1, + Name: "admin", + Email: "admin@gmail.com", + Password: "admin12345", + TelephoneNumber: "08123456789", + } + + updatedAdmin := entities.Admin{ + ID: 1, + Name: "updated_admin", + Email: "admin@gmail.com", + Password: "admin", + TelephoneNumber: "08123456780", + } + + mockAdminRepo.On("GetAdminByID", 1).Return(&admin, nil) + + result, err := AdminUseCase.UpdateAdmin(1, &updatedAdmin) + assert.Error(t, constants.ErrPasswordMustBeAtLeast8Characters, err) + assert.Equal(t, entities.Admin{}, result) + + mockAdminRepo.AssertExpectations(t) + }) +} diff --git a/usecases/category/category.go b/usecases/category/category.go index 965e549..cfb6633 100644 --- a/usecases/category/category.go +++ b/usecases/category/category.go @@ -41,6 +41,10 @@ func (uc *CategoryUseCase) CreateCategory(category *entities.Category) (*entitie return nil, constants.ErrInternalServerError } + if category == nil { + return nil, nil + } + if category.Name == "" || category.Description == "" { return nil, constants.ErrAllFieldsMustBeFilled } @@ -52,11 +56,7 @@ func (uc *CategoryUseCase) UpdateCategory(id int, newCategory *entities.Category existingCategory, err := uc.repository.GetByID(id) if err != nil { - if errors.Is(err, constants.ErrNotFound) { - return nil, constants.ErrCategoryNotFound - } return nil, constants.ErrInternalServerError - } if newCategory.Name == "" && newCategory.Description == "" { @@ -86,16 +86,11 @@ func (uc *CategoryUseCase) UpdateCategory(id int, newCategory *entities.Category func (uc *CategoryUseCase) DeleteCategory(id int) error { _, err := uc.repository.GetByID(id) if err != nil { - if errors.Is(err, constants.ErrNotFound) { - return constants.ErrNotFound - } - return constants.ErrInternalServerError + return constants.ErrCategoryNotFound } - err = uc.repository.DeleteCategory(id) if err != nil { - return constants.ErrInternalServerError + return err } - return nil } diff --git a/usecases/category/category_test.go b/usecases/category/category_test.go new file mode 100644 index 0000000..ae73b85 --- /dev/null +++ b/usecases/category/category_test.go @@ -0,0 +1,403 @@ +package category + +import ( + "e-complaint-api/constants" + "e-complaint-api/entities" + "errors" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "testing" +) + +type MockCategory struct { + mock.Mock +} + +func (m *MockCategory) GetAll() ([]entities.Category, error) { + args := m.Called() + return args.Get(0).([]entities.Category), args.Error(1) +} + +func (m *MockCategory) GetByID(id int) (entities.Category, error) { + args := m.Called(id) + return args.Get(0).(entities.Category), args.Error(1) +} + +func (m *MockCategory) CreateCategory(category *entities.Category) (*entities.Category, error) { + args := m.Called(category) + return args.Get(0).(*entities.Category), args.Error(1) +} + +func (m *MockCategory) UpdateCategory(id int, newCategory *entities.Category) (*entities.Category, error) { + args := m.Called(id, newCategory) + return args.Get(0).(*entities.Category), args.Error(1) +} + +func (m *MockCategory) DeleteCategory(id int) error { + args := m.Called(id) + return args.Error(0) +} + +func TestCategoryUseCase_GetAll(t *testing.T) { + t.Run("success", func(t *testing.T) { + mockCategory := new(MockCategory) + mockUseCase := NewCategoryUseCase(mockCategory) + + category := []entities.Category{ + { + ID: 1, + Name: "Category 1", + Description: "Description 1", + }, + { + ID: 2, + Name: "Category 2", + Description: "Description 2", + }, + } + + mockCategory.On("GetAll").Return(category, nil) + result, err := mockUseCase.GetAll() + assert.NoError(t, err) + assert.Equal(t, category, result) + mockCategory.AssertExpectations(t) + }) + + t.Run("internal server error", func(t *testing.T) { + mockCategory := new(MockCategory) + mockUseCase := NewCategoryUseCase(mockCategory) + + mockCategory.On("GetAll").Return([]entities.Category{}, constants.ErrInternalServerError) + result, err := mockUseCase.GetAll() + assert.Error(t, err) + assert.Equal(t, []entities.Category{}, result) + mockCategory.AssertExpectations(t) + + }) + +} + +func TestCategoryUseCase_GetByID(t *testing.T) { + t.Run("success", func(t *testing.T) { + mockCategory := new(MockCategory) + mockUseCase := NewCategoryUseCase(mockCategory) + + category := entities.Category{ + ID: 1, + Name: "Category 1", + Description: "Description 1", + } + + mockCategory.On("GetByID", 1).Return(category, nil) + result, err := mockUseCase.GetByID(1) + assert.NoError(t, err) + assert.Equal(t, category, result) + mockCategory.AssertExpectations(t) + }) + + t.Run("category not found", func(t *testing.T) { + mockCategory := new(MockCategory) + mockUseCase := NewCategoryUseCase(mockCategory) + + mockCategory.On("GetByID", 1).Return(entities.Category{}, constants.ErrCategoryNotFound) + result, err := mockUseCase.GetByID(1) + assert.Error(t, err) + assert.Equal(t, entities.Category{}, result) + mockCategory.AssertExpectations(t) + }) + + t.Run("internal server error", func(t *testing.T) { + mockCategory := new(MockCategory) + mockUseCase := NewCategoryUseCase(mockCategory) + + mockCategory.On("GetByID", 1).Return(entities.Category{}, constants.ErrInternalServerError) + result, err := mockUseCase.GetByID(1) + assert.Error(t, err) + assert.Equal(t, entities.Category{}, result) + mockCategory.AssertExpectations(t) + }) + +} + +func TestCategoryUseCase_CreateCategory(t *testing.T) { + t.Run("success", func(t *testing.T) { + mock := new(MockCategory) + useCase := NewCategoryUseCase(mock) + category := entities.Category{ + Name: "Category 1", + Description: "Description 1", + } + mock.On("CreateCategory", &category).Return(&category, nil) + result, err := useCase.CreateCategory(&category) + assert.NoError(t, err) + assert.Equal(t, &category, result) + mock.AssertExpectations(t) + }) + + t.Run("internal server error", func(t *testing.T) { + mock := new(MockCategory) + useCase := NewCategoryUseCase(mock) + category := entities.Category{ + Name: "Category 1", + Description: "Description 1", + } + mock.On("CreateCategory", &category).Return((*entities.Category)(nil), constants.ErrInternalServerError) + result, err := useCase.CreateCategory(&category) + assert.Error(t, err) + assert.Nil(t, result) + mock.AssertExpectations(t) + }) + + t.Run("all fields must be filled", func(t *testing.T) { + mockCategory := new(MockCategory) + mockUseCase := NewCategoryUseCase(mockCategory) + + category := entities.Category{ + Name: "", + Description: "", + } + + mockCategory.On("CreateCategory", &category).Return((*entities.Category)(nil), constants.ErrAllFieldsMustBeFilled) + result, err := mockUseCase.CreateCategory(&category) + assert.Error(t, err) + assert.Nil(t, result) + mockCategory.AssertExpectations(t) + }) + + t.Run("repository returns nil without error", func(t *testing.T) { + mockCategory := new(MockCategory) + mockUseCase := NewCategoryUseCase(mockCategory) + + category := entities.Category{ + Name: "Category 1", + Description: "Description 1", + } + + mockCategory.On("CreateCategory", &category).Return((*entities.Category)(nil), nil) + result, err := mockUseCase.CreateCategory(&category) + assert.NoError(t, err) + assert.Nil(t, result) + mockCategory.AssertExpectations(t) + }) + + t.Run("name is empty", func(t *testing.T) { + mockCategory := new(MockCategory) + mockUseCase := NewCategoryUseCase(mockCategory) + + category := entities.Category{ + Name: "", + Description: "Description 1", + } + + mockCategory.On("CreateCategory", &category).Return(&category, nil) + result, err := mockUseCase.CreateCategory(&category) + assert.Error(t, err) + assert.Nil(t, result) + mockCategory.AssertExpectations(t) + }) + + t.Run("description is empty", func(t *testing.T) { + mockCategory := new(MockCategory) + mockUseCase := NewCategoryUseCase(mockCategory) + + category := entities.Category{ + Name: "Category 1", + Description: "", + } + + mockCategory.On("CreateCategory", &category).Return(&category, nil) + result, err := mockUseCase.CreateCategory(&category) + assert.Error(t, err) + assert.Nil(t, result) + mockCategory.AssertExpectations(t) + }) + +} + +func TestCategoryUseCase_UpdateCategory(t *testing.T) { + t.Run("success", func(t *testing.T) { + mock := new(MockCategory) + useCase := NewCategoryUseCase(mock) + category := entities.Category{ + ID: 1, + Name: "Update Category 1", + Description: "Description Update 1", + } + + updatedCategory := entities.Category{ + ID: 1, + Name: "Updated Category 1", + Description: "Updated Description 1", + } + + mock.On("GetByID", 1).Return(category, nil) + mock.On("UpdateCategory", 1, &updatedCategory).Return(&updatedCategory, nil) + result, err := useCase.UpdateCategory(1, &updatedCategory) + assert.NoError(t, err) + assert.Equal(t, &updatedCategory, result) + mock.AssertExpectations(t) + }) + + t.Run("fields must be filled", func(t *testing.T) { + mockCategory := new(MockCategory) + mockUseCase := NewCategoryUseCase(mockCategory) + + category := entities.Category{ + Name: "", + Description: "", + } + + mockCategory.On("GetByID", 1).Return(entities.Category{}, nil) + result, err := mockUseCase.UpdateCategory(1, &category) + assert.Error(t, err) + assert.Nil(t, result) + mockCategory.AssertExpectations(t) + }) + + t.Run("category not found", func(t *testing.T) { + mockCategory := new(MockCategory) + mockUseCase := NewCategoryUseCase(mockCategory) + + category := entities.Category{ + Name: "Category 1", + Description: "Description 1", + } + + mockCategory.On("GetByID", 1).Return(entities.Category{}, constants.ErrCategoryNotFound) + result, err := mockUseCase.UpdateCategory(1, &category) + assert.Error(t, err) + assert.Nil(t, result) + mockCategory.AssertExpectations(t) + }) + + t.Run("no changes detected", func(t *testing.T) { + mock := new(MockCategory) + useCase := NewCategoryUseCase(mock) + + category := entities.Category{ + ID: 1, + Name: "Update Category 1", + Description: "Description Update 1", + } + + updatedCategory := entities.Category{ + ID: 1, + Name: "Updated Category 1", + Description: "Description Update 1", + } + + mock.On("GetByID", 1).Return(category, nil) + mock.On("UpdateCategory", 1, &updatedCategory).Return(&updatedCategory, errors.New("no changes detected")) + result, err := useCase.UpdateCategory(1, &updatedCategory) + assert.Error(t, err) + assert.Nil(t, result) + mock.AssertExpectations(t) + }) + + t.Run("name is empty", func(t *testing.T) { + mockCategory := new(MockCategory) + mockUseCase := NewCategoryUseCase(mockCategory) + + category := entities.Category{ + Name: "", + Description: "Description 1", + } + + mockCategory.On("GetByID", 1).Return(category, nil) + result, err := mockUseCase.UpdateCategory(1, &category) + assert.Error(t, err) + assert.Nil(t, result) + mockCategory.AssertExpectations(t) + }) + + t.Run("description is empty", func(t *testing.T) { + mockCategory := new(MockCategory) + mockUseCase := NewCategoryUseCase(mockCategory) + + category := entities.Category{ + Name: "Category 1", + Description: "", + } + + mockCategory.On("GetByID", 1).Return(category, nil) + result, err := mockUseCase.UpdateCategory(1, &category) + assert.Error(t, err) + assert.Nil(t, result) + mockCategory.AssertExpectations(t) + }) + + t.Run("name and description are empty", func(t *testing.T) { + mockCategory := new(MockCategory) + mockUseCase := NewCategoryUseCase(mockCategory) + + category := entities.Category{ + Name: "", + Description: "", + } + + mockCategory.On("GetByID", 1).Return(category, nil) + result, err := mockUseCase.UpdateCategory(1, &category) + assert.Error(t, err) + assert.Nil(t, result) + mockCategory.AssertExpectations(t) + }) + + t.Run("internal server error", func(t *testing.T) { + mockCategory := new(MockCategory) + mockUseCase := NewCategoryUseCase(mockCategory) + + category := entities.Category{ + Name: "Category 1", + Description: "Description 1", + } + + mockCategory.On("GetByID", 1).Return(entities.Category{}, constants.ErrInternalServerError) + result, err := mockUseCase.UpdateCategory(1, &category) + assert.Error(t, err) + assert.Nil(t, result) + }) +} + +func TestCategoryUseCase_DeleteCategory(t *testing.T) { + t.Run("success", func(t *testing.T) { + mock := new(MockCategory) + useCase := NewCategoryUseCase(mock) + + mock.On("GetByID", 1).Return(entities.Category{}, nil) + mock.On("DeleteCategory", 1).Return(nil) + err := useCase.DeleteCategory(1) + assert.NoError(t, err) + mock.AssertExpectations(t) + }) + + t.Run("category not found", func(t *testing.T) { + mockCategory := new(MockCategory) + mockUseCase := NewCategoryUseCase(mockCategory) + + mockCategory.On("GetByID", 1).Return(entities.Category{}, constants.ErrCategoryNotFound) + err := mockUseCase.DeleteCategory(1) + assert.Error(t, err) + mockCategory.AssertExpectations(t) + }) + + t.Run("internal server error", func(t *testing.T) { + mockCategory := new(MockCategory) + mockUseCase := NewCategoryUseCase(mockCategory) + + mockCategory.On("GetByID", 1).Return(entities.Category{}, constants.ErrInternalServerError) + err := mockUseCase.DeleteCategory(1) + assert.Error(t, err) + mockCategory.AssertExpectations(t) + }) + + t.Run("delete category error", func(t *testing.T) { + mockCategory := new(MockCategory) + mockUseCase := NewCategoryUseCase(mockCategory) + + mockCategory.On("GetByID", 1).Return(entities.Category{}, nil) + mockCategory.On("DeleteCategory", 1).Return(constants.ErrInternalServerError) + err := mockUseCase.DeleteCategory(1) + assert.Error(t, err) + mockCategory.AssertExpectations(t) + }) +} diff --git a/usecases/chatbot/chatbot_test.go b/usecases/chatbot/chatbot_test.go new file mode 100644 index 0000000..391a2d3 --- /dev/null +++ b/usecases/chatbot/chatbot_test.go @@ -0,0 +1,312 @@ +package chatbot + +import ( + "e-complaint-api/constants" + "e-complaint-api/entities" + "errors" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "testing" + "time" +) + +type Chatbot struct { + mock.Mock +} + +func (m *Chatbot) Create(chatbot *entities.Chatbot) error { + args := m.Called(chatbot) + return args.Error(0) + +} + +func (m *Chatbot) ClearHistory(userID int) error { + args := m.Called(userID) + return args.Error(0) +} + +func (m *Chatbot) GetChatCompletion(chatbot *Chatbot) error { + args := m.Called(chatbot) + return args.Error(0) +} + +func (m *Chatbot) GetHistory(userID int) ([]entities.Chatbot, error) { + args := m.Called(userID) + return args.Get(0).([]entities.Chatbot), args.Error(1) +} + +func (m *Chatbot) GetChatbotByID(id int) (*entities.Chatbot, error) { + args := m.Called(id) + return args.Get(0).(*entities.Chatbot), args.Error(1) +} + +type Faq struct { + mock.Mock +} + +func (m *Faq) GetAll() ([]entities.Faq, error) { + args := m.Called() + return args.Get(0).([]entities.Faq), args.Error(1) +} + +type Complaint struct { + mock.Mock +} + +func (m *Complaint) GetPaginated(limit int, page int, search string, filter map[string]interface{}, sortBy string, sortType string) ([]entities.Complaint, error) { + args := m.Called(limit, page, search, filter, sortBy, sortType) + return args.Get(0).([]entities.Complaint), args.Error(1) +} + +func (m *Complaint) GetMetaData(limit int, page int, search string, filter map[string]interface{}) (entities.Metadata, error) { + args := m.Called(limit, page, search, filter) + return args.Get(0).(entities.Metadata), args.Error(1) +} + +func (m *Complaint) GetByID(id string) (entities.Complaint, error) { + args := m.Called(id) + return args.Get(0).(entities.Complaint), args.Error(1) +} + +func (m *Complaint) GetByUserID(userId int) ([]entities.Complaint, error) { + args := m.Called(userId) + return args.Get(0).([]entities.Complaint), args.Error(1) +} + +func (m *Complaint) Create(complaint *entities.Complaint) error { + args := m.Called(complaint) + return args.Error(0) +} + +func (m *Complaint) Delete(id string, userId int) error { + args := m.Called(id, userId) + return args.Error(0) +} + +func (m *Complaint) AdminDelete(id string) error { + args := m.Called(id) + return args.Error(0) +} + +func (m *Complaint) Update(complaint entities.Complaint) (entities.Complaint, error) { + args := m.Called(complaint) + return args.Get(0).(entities.Complaint), args.Error(1) +} + +func (m *Complaint) UpdateStatus(id string, status string) error { + args := m.Called(id, status) + return args.Error(0) +} + +func (m *Complaint) GetStatus(id string) (string, error) { + args := m.Called(id) + return args.String(0), args.Error(1) +} + +func (m *Complaint) Import(complaints []entities.Complaint) error { + args := m.Called(complaints) + return args.Error(0) +} + +func (m *Complaint) IncreaseTotalLikes(id string) error { + args := m.Called(id) + return args.Error(0) +} + +func (m *Complaint) DecreaseTotalLikes(id string) error { + args := m.Called(id) + return args.Error(0) +} + +func (m *Complaint) GetComplaintIDsByUserID(userId int) ([]string, error) { + args := m.Called(userId) + return args.Get(0).([]string), args.Error(1) +} + +type OpenAIAPI struct { + mock.Mock +} + +func (m *OpenAIAPI) GetChatCompletion(prompt []string, userMessage string) (string, error) { + args := m.Called(prompt, userMessage) + return args.String(0), args.Error(1) +} + +func TestChatbotUseCase_ClearHistory(t *testing.T) { + t.Run("success", func(t *testing.T) { + chatbotRepo := new(Chatbot) + chatbotRepo.On("ClearHistory", 1).Return(nil) + + uc := NewChatbotUseCase(chatbotRepo, nil, nil, nil) + + err := uc.ClearHistory(1) + assert.Nil(t, err) + }) + + t.Run("error", func(t *testing.T) { + chatbotRepo := new(Chatbot) + chatbotRepo.On("ClearHistory", 1).Return(constants.ErrInternalServerError) + + uc := NewChatbotUseCase(chatbotRepo, nil, nil, nil) + + err := uc.ClearHistory(1) + assert.Equal(t, constants.ErrInternalServerError, err) + }) +} + +func TestChatbotUseCase_GetChatCompletion(t *testing.T) { + t.Run("success", func(t *testing.T) { + chatbotRepo := new(Chatbot) + faqRepo := new(Faq) + complaintRepo := new(Complaint) + openAIAPI := new(OpenAIAPI) + + chatbot := &entities.Chatbot{UserID: 1, UserMessage: "Hello"} + + faqRepo.On("GetAll").Return([]entities.Faq{}, nil) + complaintRepo.On("GetByUserID", chatbot.UserID).Return([]entities.Complaint{}, nil) + openAIAPI.On("GetChatCompletion", mock.Anything, chatbot.UserMessage).Return("Hello, how can I assist you?", nil) + chatbotRepo.On("Create", chatbot).Return(nil) + + uc := NewChatbotUseCase(chatbotRepo, faqRepo, complaintRepo, openAIAPI) + + err := uc.GetChatCompletion(chatbot) + assert.Nil(t, err) + assert.Equal(t, "Hello, how can I assist you?", chatbot.BotResponse) + }) + + t.Run("success with user complaints", func(t *testing.T) { + chatbotRepo := new(Chatbot) + faqRepo := new(Faq) + complaintRepo := new(Complaint) + openAIAPI := new(OpenAIAPI) + + chatbot := &entities.Chatbot{UserID: 1, UserMessage: "Hello"} + + faqRepo.On("GetAll").Return([]entities.Faq{}, nil) + complaintRepo.On("GetByUserID", chatbot.UserID).Return([]entities.Complaint{{ID: "1", Description: "Test", Status: "Open", CreatedAt: time.Now()}}, nil) + openAIAPI.On("GetChatCompletion", mock.Anything, chatbot.UserMessage).Return("Hello, how can I assist you?", nil) + chatbotRepo.On("Create", chatbot).Return(nil) + + uc := NewChatbotUseCase(chatbotRepo, faqRepo, complaintRepo, openAIAPI) + + err := uc.GetChatCompletion(chatbot) + assert.Nil(t, err) + assert.Equal(t, "Hello, how can I assist you?", chatbot.BotResponse) + }) + + t.Run("success with FAQs", func(t *testing.T) { + chatbotRepo := new(Chatbot) + faqRepo := new(Faq) + complaintRepo := new(Complaint) + openAIAPI := new(OpenAIAPI) + + chatbot := &entities.Chatbot{UserID: 1, UserMessage: "Hello"} + + faqRepo.On("GetAll").Return([]entities.Faq{{Question: "Test", Answer: "Test"}}, nil) + complaintRepo.On("GetByUserID", chatbot.UserID).Return([]entities.Complaint{}, nil) + openAIAPI.On("GetChatCompletion", mock.Anything, chatbot.UserMessage).Return("Hello, how can I assist you?", nil) + chatbotRepo.On("Create", chatbot).Return(nil) + + uc := NewChatbotUseCase(chatbotRepo, faqRepo, complaintRepo, openAIAPI) + + err := uc.GetChatCompletion(chatbot) + assert.Nil(t, err) + assert.Equal(t, "Hello, how can I assist you?", chatbot.BotResponse) + }) + + t.Run("error", func(t *testing.T) { + chatbotRepo := new(Chatbot) + faqRepo := new(Faq) + complaintRepo := new(Complaint) + openAIAPI := new(OpenAIAPI) + + chatbot := &entities.Chatbot{UserID: 1, UserMessage: "Hello"} + + faqRepo.On("GetAll").Return([]entities.Faq{}, errors.New("error")) + uc := NewChatbotUseCase(chatbotRepo, faqRepo, complaintRepo, openAIAPI) + + err := uc.GetChatCompletion(chatbot) + assert.NotNil(t, err) + }) + + t.Run("error on GetChatCompletion", func(t *testing.T) { + chatbotRepo := new(Chatbot) + faqRepo := new(Faq) + complaintRepo := new(Complaint) + openAIAPI := new(OpenAIAPI) + + chatbot := &entities.Chatbot{UserID: 1, UserMessage: "Hello"} + + faqRepo.On("GetAll").Return([]entities.Faq{}, nil) + complaintRepo.On("GetByUserID", chatbot.UserID).Return([]entities.Complaint{}, nil) + openAIAPI.On("GetChatCompletion", mock.Anything, chatbot.UserMessage).Return("", errors.New("error")) + + uc := NewChatbotUseCase(chatbotRepo, faqRepo, complaintRepo, openAIAPI) + + err := uc.GetChatCompletion(chatbot) + assert.NotNil(t, err) + }) + + t.Run("error on Create", func(t *testing.T) { + chatbotRepo := new(Chatbot) + faqRepo := new(Faq) + complaintRepo := new(Complaint) + openAIAPI := new(OpenAIAPI) + + chatbot := &entities.Chatbot{UserID: 1, UserMessage: "Hello"} + + faqRepo.On("GetAll").Return([]entities.Faq{}, nil) + complaintRepo.On("GetByUserID", chatbot.UserID).Return([]entities.Complaint{}, nil) + openAIAPI.On("GetChatCompletion", mock.Anything, chatbot.UserMessage).Return("Hello, how can I assist you?", nil) + chatbotRepo.On("Create", chatbot).Return(errors.New("error")) + + uc := NewChatbotUseCase(chatbotRepo, faqRepo, complaintRepo, openAIAPI) + + err := uc.GetChatCompletion(chatbot) + assert.NotNil(t, err) + }) + + t.Run("error_on_GetByUserID", func(t *testing.T) { + chatbotRepo := new(Chatbot) + faqRepo := new(Faq) + complaintRepo := new(Complaint) + openAIAPI := new(OpenAIAPI) + + chatbot := &entities.Chatbot{UserID: 1, UserMessage: "Hello"} + + faqRepo.On("GetAll").Return([]entities.Faq{}, nil) + complaintRepo.On("GetByUserID", chatbot.UserID).Return([]entities.Complaint{}, errors.New("error")) + + uc := NewChatbotUseCase(chatbotRepo, faqRepo, complaintRepo, openAIAPI) + + err := uc.GetChatCompletion(chatbot) + assert.NotNil(t, err) + }) + +} + +func TestChatbotUseCase_GetHistory(t *testing.T) { + t.Run("success", func(t *testing.T) { + chatbotRepo := new(Chatbot) + chatbotRepo.On("GetHistory", 1).Return([]entities.Chatbot{}, nil) + + uc := NewChatbotUseCase(chatbotRepo, nil, nil, nil) + + chatbots, err := uc.GetHistory(1) + assert.Nil(t, err) + assert.Equal(t, []entities.Chatbot{}, chatbots) + }) + + t.Run("error", func(t *testing.T) { + chatbotRepo := new(Chatbot) + chatbotRepo.On("GetHistory", 1).Return([]entities.Chatbot{}, constants.ErrInternalServerError) + + uc := NewChatbotUseCase(chatbotRepo, nil, nil, nil) + + chatbots, err := uc.GetHistory(1) + assert.NotNil(t, err) + assert.Nil(t, chatbots) + }) + +} diff --git a/usecases/complaint/complaint.go b/usecases/complaint/complaint.go index b0f55b2..4816c69 100644 --- a/usecases/complaint/complaint.go +++ b/usecases/complaint/complaint.go @@ -4,24 +4,23 @@ import ( "e-complaint-api/constants" "e-complaint-api/entities" "e-complaint-api/utils" - "io" "mime/multipart" - "os" "strconv" "strings" - - "github.com/xuri/excelize/v2" + "time" ) type ComplaintUseCase struct { complaintRepo entities.ComplaintRepositoryInterface complaintFileRepo entities.ComplaintFileRepositoryInterface + getRowsFromExcel func(file *multipart.FileHeader) ([][]string, error) } func NewComplaintUseCase(complaintRepo entities.ComplaintRepositoryInterface, complaintFileRepo entities.ComplaintFileRepositoryInterface) *ComplaintUseCase { return &ComplaintUseCase{ complaintRepo: complaintRepo, complaintFileRepo: complaintFileRepo, + getRowsFromExcel: utils.GetRowsFromExcel, } } @@ -60,7 +59,10 @@ func (u *ComplaintUseCase) GetMetaData(limit int, page int, search string, filte pagination.FirstPage = 1 pagination.LastPage = (metaData.TotalData + limit - 1) / limit pagination.CurrentPage = page - if pagination.CurrentPage == pagination.LastPage { + if metaData.TotalData == 0 { + pagination.TotalDataPerPage = 0 + pagination.LastPage = 1 + } else if pagination.CurrentPage == pagination.LastPage { pagination.TotalDataPerPage = metaData.TotalData - (pagination.LastPage-1)*limit } else { pagination.TotalDataPerPage = limit @@ -181,37 +183,9 @@ func (u *ComplaintUseCase) UpdateStatus(id string, status string) error { } func (u *ComplaintUseCase) Import(file *multipart.FileHeader) error { - // Open the file from the multipart.FileHeader - f, err := file.Open() - if err != nil { - return constants.ErrInternalServerError - } - defer f.Close() - - // Create a temporary file to copy the contents - tempFile, err := os.CreateTemp("", "uploaded-*.xlsx") - if err != nil { - return constants.ErrInternalServerError - } - defer os.Remove(tempFile.Name()) // Clean up the temp file - defer tempFile.Close() - - // Copy the file contents to the temporary file - if _, err := io.Copy(tempFile, f); err != nil { - return constants.ErrInternalServerError - } - - // Open the temporary file using excelize - excelFile, err := excelize.OpenFile(tempFile.Name()) + rows, err := u.getRowsFromExcel(file) if err != nil { - return constants.ErrInternalServerError - } - defer excelFile.Close() - - // Get all rows in the "Sheet1" - rows, err := excelFile.GetRows("Sheet1") - if err != nil { - return constants.ErrInternalServerError + return err } var complaints []entities.Complaint @@ -225,19 +199,19 @@ func (u *ComplaintUseCase) Import(file *multipart.FileHeader) error { continue } - if len(row) < 6 { + if len(row) < 9 { // Ensure the row has enough columns - return constants.ErrInternalServerError + return constants.ErrColumnsDoesntMatch } userId, err := strconv.Atoi(row[0]) if err != nil { - return constants.ErrInternalServerError + return constants.ErrInvalidIDFormat } categoryId, err := strconv.Atoi(row[1]) if err != nil { - return constants.ErrInternalServerError + return constants.ErrInvalidCategoryIDFormat } regencyId := row[2] @@ -245,16 +219,27 @@ func (u *ComplaintUseCase) Import(file *multipart.FileHeader) error { description := row[4] status := row[5] typeComplaint := row[6] - pathFiles := row[7] + date, _ := time.Parse("02-01-2006", row[7]) + pathFiles := row[8] process = []entities.ComplaintProcess{} if status == "Verifikasi" { + process = append(process, entities.ComplaintProcess{ + AdminID: 1, + Status: "Pending", + Message: "Aduan anda sedang dalam proses verifikasi oleh admin kami", + }) process = append(process, entities.ComplaintProcess{ AdminID: 1, Status: "Verifikasi", Message: "Aduan anda telah diverifikasi oleh admin kami", }) } else if status == "On Progress" { + process = append(process, entities.ComplaintProcess{ + AdminID: 1, + Status: "Pending", + Message: "Aduan anda sedang dalam proses verifikasi oleh admin kami", + }) process = append(process, entities.ComplaintProcess{ AdminID: 1, Status: "Verifikasi", @@ -266,6 +251,11 @@ func (u *ComplaintUseCase) Import(file *multipart.FileHeader) error { Message: "Aduan anda sedang dalam proses penanganan", }) } else if status == "Selesai" { + process = append(process, entities.ComplaintProcess{ + AdminID: 1, + Status: "Pending", + Message: "Aduan anda sedang dalam proses verifikasi oleh admin kami", + }) process = append(process, entities.ComplaintProcess{ AdminID: 1, Status: "Verifikasi", @@ -282,11 +272,22 @@ func (u *ComplaintUseCase) Import(file *multipart.FileHeader) error { Message: "Aduan anda telah selesai ditangani", }) } else if status == "Ditolak" { + process = append(process, entities.ComplaintProcess{ + AdminID: 1, + Status: "Pending", + Message: "Aduan anda sedang dalam proses verifikasi oleh admin kami", + }) process = append(process, entities.ComplaintProcess{ AdminID: 1, Status: "Ditolak", Message: "Aduan anda ditolak karena tidak sesuai dengan ketentuan yang berlaku", }) + } else if status == "Pending" { + process = append(process, entities.ComplaintProcess{ + AdminID: 1, + Status: "Pending", + Message: "Aduan anda sedang dalam proses verifikasi oleh admin kami", + }) } // split pathFiles by comma @@ -309,6 +310,7 @@ func (u *ComplaintUseCase) Import(file *multipart.FileHeader) error { Description: description, Status: status, Type: typeComplaint, + Date: date, Files: complaintFiles, Process: process, } diff --git a/usecases/complaint/complaint_test.go b/usecases/complaint/complaint_test.go new file mode 100644 index 0000000..be16f94 --- /dev/null +++ b/usecases/complaint/complaint_test.go @@ -0,0 +1,1083 @@ +package complaint + +import ( + "e-complaint-api/constants" + "e-complaint-api/entities" + "errors" + "mime/multipart" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +type MockComplaintRepo struct { + mock.Mock +} + +func (m *MockComplaintRepo) GetPaginated(limit int, page int, search string, filter map[string]interface{}, sortBy string, sortType string) ([]entities.Complaint, error) { + args := m.Called(limit, page, search, filter, sortBy, sortType) + return args.Get(0).([]entities.Complaint), args.Error(1) +} + +func (m *MockComplaintRepo) GetMetaData(limit int, page int, search string, filter map[string]interface{}) (entities.Metadata, error) { + args := m.Called(limit, page, search, filter) + return args.Get(0).(entities.Metadata), args.Error(1) +} + +func (m *MockComplaintRepo) GetByID(id string) (entities.Complaint, error) { + args := m.Called(id) + return args.Get(0).(entities.Complaint), args.Error(1) +} + +func (m *MockComplaintRepo) GetByUserID(userId int) ([]entities.Complaint, error) { + args := m.Called(userId) + return args.Get(0).([]entities.Complaint), args.Error(1) +} + +func (m *MockComplaintRepo) Create(complaint *entities.Complaint) error { + args := m.Called(complaint) + return args.Error(0) +} + +func (m *MockComplaintRepo) Delete(id string, userId int) error { + args := m.Called(id, userId) + return args.Error(0) +} + +func (m *MockComplaintRepo) AdminDelete(id string) error { + args := m.Called(id) + return args.Error(0) +} + +func (m *MockComplaintRepo) Update(complaint entities.Complaint) (entities.Complaint, error) { + args := m.Called(complaint) + return args.Get(0).(entities.Complaint), args.Error(1) +} + +func (m *MockComplaintRepo) UpdateStatus(id string, status string) error { + args := m.Called(id, status) + return args.Error(0) +} + +func (m *MockComplaintRepo) GetStatus(id string) (string, error) { + args := m.Called(id) + return args.String(0), args.Error(1) +} + +func (m *MockComplaintRepo) Import(complaints []entities.Complaint) error { + args := m.Called(complaints) + return args.Error(0) +} + +func (m *MockComplaintRepo) IncreaseTotalLikes(id string) error { + args := m.Called(id) + return args.Error(0) +} + +func (m *MockComplaintRepo) DecreaseTotalLikes(id string) error { + args := m.Called(id) + return args.Error(0) +} + +func (m *MockComplaintRepo) GetComplaintIDsByUserID(userId int) ([]string, error) { + args := m.Called(userId) + return args.Get(0).([]string), args.Error(1) +} + +type MockComplaintFileRepo struct { + mock.Mock +} + +func (m *MockComplaintFileRepo) Create(complaintFiles []*entities.ComplaintFile) error { + args := m.Called(complaintFiles) + return args.Error(0) +} + +func (m *MockComplaintFileRepo) DeleteByComplaintID(complaintID string) error { + args := m.Called(complaintID) + return args.Error(0) +} + +type MockUtils struct { + mock.Mock +} + +func (m *MockUtils) GetRowsFromExcel(file *multipart.FileHeader) ([][]string, error) { + args := m.Called(file) + return args.Get(0).([][]string), args.Error(1) +} + +func TestGetPaginated(t *testing.T) { + t.Run("success", func(t *testing.T) { + mockComplaintRepo := new(MockComplaintRepo) + mockComplaintFileRepo := new(MockComplaintFileRepo) + mockUsecase := NewComplaintUseCase(mockComplaintRepo, mockComplaintFileRepo) + + mockComplaintRepo.On("GetPaginated", 10, 1, "", map[string]interface{}{}, "created_at", "desc").Return([]entities.Complaint{}, nil) + + result, err := mockUsecase.GetPaginated(10, 1, "", map[string]interface{}{}, "created_at", "desc") + assert.NoError(t, err) + assert.Equal(t, []entities.Complaint{}, result) + + mockComplaintRepo.AssertExpectations(t) + mockComplaintFileRepo.AssertExpectations(t) + }) + + t.Run("success with empty sortBy and sortType", func(t *testing.T) { + mockComplaintRepo := new(MockComplaintRepo) + mockComplaintFileRepo := new(MockComplaintFileRepo) + + mockUsecase := NewComplaintUseCase(mockComplaintRepo, mockComplaintFileRepo) + + mockComplaintRepo.On("GetPaginated", 10, 1, "", map[string]interface{}{}, "created_at", "DESC").Return([]entities.Complaint{}, nil) + + result, err := mockUsecase.GetPaginated(10, 1, "", map[string]interface{}{}, "", "") + assert.NoError(t, err) + assert.Equal(t, []entities.Complaint{}, result) + + mockComplaintRepo.AssertExpectations(t) + }) + + t.Run("failed limit must filled when page is filled", func(t *testing.T) { + mockComplaintRepo := new(MockComplaintRepo) + mockComplaintFileRepo := new(MockComplaintFileRepo) + mockUsecase := NewComplaintUseCase(mockComplaintRepo, mockComplaintFileRepo) + + result, err := mockUsecase.GetPaginated(0, 1, "", map[string]interface{}{}, "created_at", "desc") + assert.Error(t, err) + assert.Equal(t, constants.ErrLimitMustBeFilled, err) + assert.Nil(t, result) + + mockComplaintRepo.AssertExpectations(t) + mockComplaintFileRepo.AssertExpectations(t) + }) + + t.Run("failed page must filled when limit is filled", func(t *testing.T) { + mockComplaintRepo := new(MockComplaintRepo) + mockComplaintFileRepo := new(MockComplaintFileRepo) + mockUsecase := NewComplaintUseCase(mockComplaintRepo, mockComplaintFileRepo) + + result, err := mockUsecase.GetPaginated(10, 0, "", map[string]interface{}{}, "created_at", "desc") + assert.Error(t, err) + assert.Equal(t, constants.ErrPageMustBeFilled, err) + assert.Nil(t, result) + + mockComplaintRepo.AssertExpectations(t) + mockComplaintFileRepo.AssertExpectations(t) + }) + + t.Run("failed internal server error", func(t *testing.T) { + mockComplaintRepo := new(MockComplaintRepo) + mockComplaintFileRepo := new(MockComplaintFileRepo) + mockUsecase := NewComplaintUseCase(mockComplaintRepo, mockComplaintFileRepo) + + mockComplaintRepo.On("GetPaginated", 10, 1, "", map[string]interface{}{}, "created_at", "desc").Return(([]entities.Complaint)(nil), constants.ErrInternalServerError) + + result, err := mockUsecase.GetPaginated(10, 1, "", map[string]interface{}{}, "created_at", "desc") + assert.Error(t, err) + assert.Equal(t, constants.ErrInternalServerError, err) + assert.Nil(t, result) + + mockComplaintRepo.AssertExpectations(t) + mockComplaintFileRepo.AssertExpectations(t) + }) +} + +func TestGetMetaData(t *testing.T) { + t.Run("success empty", func(t *testing.T) { + mockComplaintRepo := new(MockComplaintRepo) + mockComplaintFileRepo := new(MockComplaintFileRepo) + mockUsecase := NewComplaintUseCase(mockComplaintRepo, mockComplaintFileRepo) + + mockComplaintRepo.On("GetMetaData", 10, 1, "", map[string]interface{}{}).Return(entities.Metadata{}, nil) + + result, err := mockUsecase.GetMetaData(10, 1, "", map[string]interface{}{}) + expectedMetadata := entities.Metadata{ + Pagination: entities.Pagination{ + FirstPage: 1, + CurrentPage: 1, + LastPage: 1, + }, + } + + assert.NoError(t, err) + assert.Equal(t, expectedMetadata, result) + + mockComplaintRepo.AssertExpectations(t) + mockComplaintFileRepo.AssertExpectations(t) + }) + + t.Run("success not empty", func(t *testing.T) { + mockComplaintRepo := new(MockComplaintRepo) + mockComplaintFileRepo := new(MockComplaintFileRepo) + mockUsecase := NewComplaintUseCase(mockComplaintRepo, mockComplaintFileRepo) + + mockComplaintRepo.On("GetMetaData", 10, 1, "", map[string]interface{}{}).Return(entities.Metadata{ + TotalData: 10, + }, nil) + + result, err := mockUsecase.GetMetaData(10, 1, "", map[string]interface{}{}) + expectedMetadata := entities.Metadata{ + TotalData: 10, + Pagination: entities.Pagination{ + FirstPage: 1, + LastPage: 1, + CurrentPage: 1, + TotalDataPerPage: 10, + PrevPage: 0, + }, + } + + assert.NoError(t, err) + assert.Equal(t, expectedMetadata, result) + + mockComplaintRepo.AssertExpectations(t) + mockComplaintFileRepo.AssertExpectations(t) + }) + + t.Run("success not empty with page > 1", func(t *testing.T) { + mockComplaintRepo := new(MockComplaintRepo) + mockComplaintFileRepo := new(MockComplaintFileRepo) + mockUsecase := NewComplaintUseCase(mockComplaintRepo, mockComplaintFileRepo) + + mockComplaintRepo.On("GetMetaData", 10, 2, "", map[string]interface{}{}).Return(entities.Metadata{ + TotalData: 10, + }, nil) + + result, err := mockUsecase.GetMetaData(10, 2, "", map[string]interface{}{}) + expectedMetadata := entities.Metadata{ + TotalData: 10, + Pagination: entities.Pagination{ + FirstPage: 1, + LastPage: 1, + CurrentPage: 2, + TotalDataPerPage: 10, + PrevPage: 1, + NextPage: 0, + }, + } + + assert.NoError(t, err) + assert.Equal(t, expectedMetadata, result) + + mockComplaintRepo.AssertExpectations(t) + mockComplaintFileRepo.AssertExpectations(t) + }) + + t.Run("success not empty with page > 1 and not in last page", func(t *testing.T) { + mockComplaintRepo := new(MockComplaintRepo) + mockComplaintFileRepo := new(MockComplaintFileRepo) + mockUsecase := NewComplaintUseCase(mockComplaintRepo, mockComplaintFileRepo) + + mockComplaintRepo.On("GetMetaData", 10, 2, "", map[string]interface{}{}).Return(entities.Metadata{ + TotalData: 30, + }, nil) + + result, err := mockUsecase.GetMetaData(10, 2, "", map[string]interface{}{}) + expectedMetadata := entities.Metadata{ + TotalData: 30, + Pagination: entities.Pagination{ + FirstPage: 1, + LastPage: 3, + CurrentPage: 2, + TotalDataPerPage: 10, + PrevPage: 1, + NextPage: 3, + }, + } + + assert.NoError(t, err) + assert.Equal(t, expectedMetadata, result) + + mockComplaintRepo.AssertExpectations(t) + mockComplaintFileRepo.AssertExpectations(t) + }) + + t.Run("success without limit and page filled", func(t *testing.T) { + mockComplaintRepo := new(MockComplaintRepo) + mockComplaintFileRepo := new(MockComplaintFileRepo) + mockUsecase := NewComplaintUseCase(mockComplaintRepo, mockComplaintFileRepo) + + mockComplaintRepo.On("GetMetaData", 0, 0, "", map[string]interface{}{}).Return(entities.Metadata{}, nil) + + result, err := mockUsecase.GetMetaData(0, 0, "", map[string]interface{}{}) + expectedMetadata := entities.Metadata{ + Pagination: entities.Pagination{ + FirstPage: 1, + CurrentPage: 1, + LastPage: 1, + }, + } + + assert.NoError(t, err) + assert.Equal(t, expectedMetadata, result) + + mockComplaintRepo.AssertExpectations(t) + mockComplaintFileRepo.AssertExpectations(t) + }) + + t.Run("failed internal server error", func(t *testing.T) { + mockComplaintRepo := new(MockComplaintRepo) + mockComplaintFileRepo := new(MockComplaintFileRepo) + mockUsecase := NewComplaintUseCase(mockComplaintRepo, mockComplaintFileRepo) + + mockComplaintRepo.On("GetMetaData", 10, 1, "", map[string]interface{}{}).Return(entities.Metadata{}, constants.ErrInternalServerError) + + result, err := mockUsecase.GetMetaData(10, 1, "", map[string]interface{}{}) + expectedMetadata := entities.Metadata{} + + assert.Error(t, err) + assert.Equal(t, constants.ErrInternalServerError, err) + assert.Equal(t, expectedMetadata, result) + + mockComplaintRepo.AssertExpectations(t) + mockComplaintFileRepo.AssertExpectations(t) + }) +} + +func TestGetByID(t *testing.T) { + t.Run("success", func(t *testing.T) { + mockComplaintRepo := new(MockComplaintRepo) + mockComplaintFileRepo := new(MockComplaintFileRepo) + mockUsecase := NewComplaintUseCase(mockComplaintRepo, mockComplaintFileRepo) + + mockComplaintRepo.On("GetByID", "1").Return(entities.Complaint{}, nil) + + result, err := mockUsecase.GetByID("1") + assert.NoError(t, err) + assert.Equal(t, entities.Complaint{}, result) + + mockComplaintRepo.AssertExpectations(t) + mockComplaintFileRepo.AssertExpectations(t) + }) + + t.Run("failed internal server error", func(t *testing.T) { + mockComplaintRepo := new(MockComplaintRepo) + mockComplaintFileRepo := new(MockComplaintFileRepo) + mockUsecase := NewComplaintUseCase(mockComplaintRepo, mockComplaintFileRepo) + + mockComplaintRepo.On("GetByID", "1").Return(entities.Complaint{}, constants.ErrInternalServerError) + + result, err := mockUsecase.GetByID("1") + assert.Error(t, err) + assert.Equal(t, constants.ErrInternalServerError, err) + assert.Equal(t, entities.Complaint{}, result) + + mockComplaintRepo.AssertExpectations(t) + mockComplaintFileRepo.AssertExpectations(t) + }) + + t.Run("failed complaint not found", func(t *testing.T) { + mockComplaintRepo := new(MockComplaintRepo) + mockComplaintFileRepo := new(MockComplaintFileRepo) + mockUsecase := NewComplaintUseCase(mockComplaintRepo, mockComplaintFileRepo) + + mockComplaintRepo.On("GetByID", "1").Return(entities.Complaint{}, constants.ErrComplaintNotFound) + + result, err := mockUsecase.GetByID("1") + assert.Error(t, err) + assert.Equal(t, constants.ErrComplaintNotFound, err) + assert.Equal(t, entities.Complaint{}, result) + + mockComplaintRepo.AssertExpectations(t) + mockComplaintFileRepo.AssertExpectations(t) + }) +} + +func TestGetByUserID(t *testing.T) { + t.Run("success empty", func(t *testing.T) { + mockComplaintRepo := new(MockComplaintRepo) + mockComplaintFileRepo := new(MockComplaintFileRepo) + mockUsecase := NewComplaintUseCase(mockComplaintRepo, mockComplaintFileRepo) + + mockComplaintRepo.On("GetByUserID", 1).Return([]entities.Complaint{}, nil) + + result, err := mockUsecase.GetByUserID(1) + assert.NoError(t, err) + assert.Equal(t, []entities.Complaint{}, result) + + mockComplaintRepo.AssertExpectations(t) + mockComplaintFileRepo.AssertExpectations(t) + }) + + t.Run("success not empty", func(t *testing.T) { + mockComplaintRepo := new(MockComplaintRepo) + mockComplaintFileRepo := new(MockComplaintFileRepo) + mockUsecase := NewComplaintUseCase(mockComplaintRepo, mockComplaintFileRepo) + + mockComplaintRepo.On("GetByUserID", 1).Return([]entities.Complaint{{ID: "1"}}, nil) + + result, err := mockUsecase.GetByUserID(1) + assert.NoError(t, err) + assert.Equal(t, []entities.Complaint{{ID: "1"}}, result) + + mockComplaintRepo.AssertExpectations(t) + mockComplaintFileRepo.AssertExpectations(t) + }) + + t.Run("failed internal server error", func(t *testing.T) { + mockComplaintRepo := new(MockComplaintRepo) + mockComplaintFileRepo := new(MockComplaintFileRepo) + mockUsecase := NewComplaintUseCase(mockComplaintRepo, mockComplaintFileRepo) + + mockComplaintRepo.On("GetByUserID", 1).Return([]entities.Complaint(nil), constants.ErrInternalServerError) + + result, err := mockUsecase.GetByUserID(1) + assert.Error(t, err) + assert.Equal(t, constants.ErrInternalServerError, err) + assert.Equal(t, []entities.Complaint(nil), result) + + mockComplaintRepo.AssertExpectations(t) + mockComplaintFileRepo.AssertExpectations(t) + }) +} + +func TestCreate(t *testing.T) { + t.Run("success", func(t *testing.T) { + date, _ := time.Parse("2006-01-02", "2021-01-01") + complaint := entities.Complaint{ + UserID: 1, + CategoryID: 1, + RegencyID: "1901", + Description: "description", + Address: "address", + Type: "public", + Date: date, + } + + mockComplaintRepo := new(MockComplaintRepo) + mockComplaintFileRepo := new(MockComplaintFileRepo) + mockUsecase := NewComplaintUseCase(mockComplaintRepo, mockComplaintFileRepo) + + mockComplaintRepo.On("Create", &complaint).Return(nil) + + result, err := mockUsecase.Create(&complaint) + assert.NoError(t, err) + assert.Equal(t, complaint, result) + + mockComplaintRepo.AssertExpectations(t) + mockComplaintFileRepo.AssertExpectations(t) + }) + + t.Run("failed all fields must be filled", func(t *testing.T) { + complaint := entities.Complaint{ + UserID: 1, + CategoryID: 1, + RegencyID: "", + Description: "description", + Address: "address", + Type: "public", + Date: time.Time{}, + } + + mockComplaintRepo := new(MockComplaintRepo) + mockComplaintFileRepo := new(MockComplaintFileRepo) + + mockUsecase := NewComplaintUseCase(mockComplaintRepo, mockComplaintFileRepo) + + result, err := mockUsecase.Create(&complaint) + assert.Error(t, constants.ErrAllFieldsMustBeFilled, err) + assert.Equal(t, entities.Complaint{}, result) + + mockComplaintRepo.AssertExpectations(t) + mockComplaintFileRepo.AssertExpectations(t) + }) + + t.Run("failed regency not found", func(t *testing.T) { + date, _ := time.Parse("2006-01-02", "2021-01-01") + complaint := entities.Complaint{ + UserID: 1, + CategoryID: 1, + RegencyID: "1901", + Description: "description", + Address: "address", + Type: "public", + Date: date, + } + + mockComplaintRepo := new(MockComplaintRepo) + mockComplaintFileRepo := new(MockComplaintFileRepo) + + mockComplaintRepo.On("Create", &complaint).Return(errors.New("REFERENCES `regencies` (`id`))")) + + mockUsecase := NewComplaintUseCase(mockComplaintRepo, mockComplaintFileRepo) + + result, err := mockUsecase.Create(&complaint) + assert.Error(t, constants.ErrRegencyNotFound, err) + assert.Equal(t, entities.Complaint{}, result) + + mockComplaintRepo.AssertExpectations(t) + mockComplaintFileRepo.AssertExpectations(t) + }) + + t.Run("failed category not found", func(t *testing.T) { + date, _ := time.Parse("2006-01-02", "2021-01-01") + complaint := entities.Complaint{ + UserID: 1, + CategoryID: 1, + RegencyID: "1901", + Description: "description", + Address: "address", + Type: "public", + Date: date, + } + + mockComplaintRepo := new(MockComplaintRepo) + mockComplaintFileRepo := new(MockComplaintFileRepo) + + mockComplaintRepo.On("Create", &complaint).Return(errors.New("REFERENCES `categories` (`id`))")) + + mockUsecase := NewComplaintUseCase(mockComplaintRepo, mockComplaintFileRepo) + + result, err := mockUsecase.Create(&complaint) + assert.Error(t, constants.ErrCategoryNotFound, err) + assert.Equal(t, entities.Complaint{}, result) + + mockComplaintRepo.AssertExpectations(t) + mockComplaintFileRepo.AssertExpectations(t) + }) + + t.Run("failed internal server error", func(t *testing.T) { + date, _ := time.Parse("2006-01-02", "2021-01-01") + complaint := entities.Complaint{ + UserID: 1, + CategoryID: 1, + RegencyID: "1901", + Description: "description", + Address: "address", + Type: "public", + Date: date, + } + + mockComplaintRepo := new(MockComplaintRepo) + mockComplaintFileRepo := new(MockComplaintFileRepo) + + mockComplaintRepo.On("Create", &complaint).Return(constants.ErrInternalServerError) + + mockUsecase := NewComplaintUseCase(mockComplaintRepo, mockComplaintFileRepo) + + result, err := mockUsecase.Create(&complaint) + assert.Error(t, constants.ErrInternalServerError, err) + assert.Equal(t, entities.Complaint{}, result) + + mockComplaintRepo.AssertExpectations(t) + mockComplaintFileRepo.AssertExpectations(t) + }) +} + +func TestDelete(t *testing.T) { + t.Run("success admin delete", func(t *testing.T) { + mockComplaintRepo := new(MockComplaintRepo) + mockComplaintFileRepo := new(MockComplaintFileRepo) + + mockComplaintRepo.On("AdminDelete", "1").Return(nil) + + mockUsecase := NewComplaintUseCase(mockComplaintRepo, mockComplaintFileRepo) + + err := mockUsecase.Delete("1", 1, "admin") + assert.NoError(t, err) + + mockComplaintRepo.AssertExpectations(t) + mockComplaintFileRepo.AssertExpectations(t) + }) + + t.Run("success user delete", func(t *testing.T) { + mockComplaintRepo := new(MockComplaintRepo) + mockComplaintFileRepo := new(MockComplaintFileRepo) + + mockComplaintRepo.On("Delete", "1", 1).Return(nil) + + mockUsecase := NewComplaintUseCase(mockComplaintRepo, mockComplaintFileRepo) + + err := mockUsecase.Delete("1", 1, "user") + assert.NoError(t, err) + + mockComplaintRepo.AssertExpectations(t) + mockComplaintFileRepo.AssertExpectations(t) + }) + + t.Run("failed internal server error admin delete", func(t *testing.T) { + mockComplaintRepo := new(MockComplaintRepo) + mockComplaintFileRepo := new(MockComplaintFileRepo) + + mockComplaintRepo.On("AdminDelete", "1").Return(constants.ErrInternalServerError) + + mockUsecase := NewComplaintUseCase(mockComplaintRepo, mockComplaintFileRepo) + + err := mockUsecase.Delete("1", 1, "admin") + assert.Error(t, constants.ErrInternalServerError, err) + + mockComplaintRepo.AssertExpectations(t) + mockComplaintFileRepo.AssertExpectations(t) + }) + + t.Run("failed internal server error user delete", func(t *testing.T) { + mockComplaintRepo := new(MockComplaintRepo) + mockComplaintFileRepo := new(MockComplaintFileRepo) + + mockComplaintRepo.On("Delete", "1", 1).Return(constants.ErrInternalServerError) + + mockUsecase := NewComplaintUseCase(mockComplaintRepo, mockComplaintFileRepo) + + err := mockUsecase.Delete("1", 1, "user") + assert.Error(t, constants.ErrInternalServerError, err) + + mockComplaintRepo.AssertExpectations(t) + mockComplaintFileRepo.AssertExpectations(t) + }) +} + +func TestUpdate(t *testing.T) { + t.Run("success", func(t *testing.T) { + date, _ := time.Parse("2006-01-02", "2021-01-01") + complaint := entities.Complaint{ + ID: "1", + UserID: 1, + CategoryID: 1, + RegencyID: "1901", + Description: "description", + Address: "address", + Type: "public", + Date: date, + } + + mockComplaintRepo := new(MockComplaintRepo) + mockComplaintFileRepo := new(MockComplaintFileRepo) + + mockComplaintRepo.On("Update", complaint).Return(complaint, nil) + + mockUsecase := NewComplaintUseCase(mockComplaintRepo, mockComplaintFileRepo) + + result, err := mockUsecase.Update(complaint) + assert.NoError(t, err) + assert.Equal(t, complaint, result) + + mockComplaintRepo.AssertExpectations(t) + mockComplaintFileRepo.AssertExpectations(t) + }) + + t.Run("failed all fields must be filled", func(t *testing.T) { + complaint := entities.Complaint{ + ID: "1", + UserID: 1, + CategoryID: 1, + RegencyID: "", + Description: "description", + Address: "address", + Type: "public", + Date: time.Time{}, + } + + mockComplaintRepo := new(MockComplaintRepo) + mockComplaintFileRepo := new(MockComplaintFileRepo) + + mockUsecase := NewComplaintUseCase(mockComplaintRepo, mockComplaintFileRepo) + + result, err := mockUsecase.Update(complaint) + assert.Error(t, constants.ErrAllFieldsMustBeFilled, err) + assert.Equal(t, entities.Complaint{}, result) + + mockComplaintRepo.AssertExpectations(t) + mockComplaintFileRepo.AssertExpectations(t) + }) + + t.Run("failed regency not found", func(t *testing.T) { + date, _ := time.Parse("2006-01-02", "2021-01-01") + complaint := entities.Complaint{ + ID: "1", + UserID: 1, + CategoryID: 1, + RegencyID: "1901", + Description: "description", + Address: "address", + Type: "public", + Date: date, + } + + mockComplaintRepo := new(MockComplaintRepo) + mockComplaintFileRepo := new(MockComplaintFileRepo) + + mockComplaintRepo.On("Update", complaint).Return(entities.Complaint{}, errors.New("REFERENCES `regencies` (`id`))")) + + mockUsecase := NewComplaintUseCase(mockComplaintRepo, mockComplaintFileRepo) + + result, err := mockUsecase.Update(complaint) + assert.Error(t, constants.ErrRegencyNotFound, err) + assert.Equal(t, entities.Complaint{}, result) + + mockComplaintRepo.AssertExpectations(t) + mockComplaintFileRepo.AssertExpectations(t) + }) + + t.Run("failed category not found", func(t *testing.T) { + date, _ := time.Parse("2006-01-02", "2021-01-01") + complaint := entities.Complaint{ + ID: "1", + UserID: 1, + CategoryID: 1, + RegencyID: "1901", + Description: "description", + Address: "address", + Type: "public", + Date: date, + } + + mockComplaintRepo := new(MockComplaintRepo) + mockComplaintFileRepo := new(MockComplaintFileRepo) + + mockComplaintRepo.On("Update", complaint).Return(entities.Complaint{}, errors.New("REFERENCES `categories` (`id`))")) + + mockUsecase := NewComplaintUseCase(mockComplaintRepo, mockComplaintFileRepo) + + result, err := mockUsecase.Update(complaint) + assert.Error(t, constants.ErrCategoryNotFound, err) + assert.Equal(t, entities.Complaint{}, result) + + mockComplaintRepo.AssertExpectations(t) + mockComplaintFileRepo.AssertExpectations(t) + }) + + t.Run("failed internal server error", func(t *testing.T) { + date, _ := time.Parse("2006-01-02", "2021-01-01") + complaint := entities.Complaint{ + ID: "1", + UserID: 1, + CategoryID: 1, + RegencyID: "1901", + Description: "description", + Address: "address", + Type: "public", + Date: date, + } + + mockComplaintRepo := new(MockComplaintRepo) + mockComplaintFileRepo := new(MockComplaintFileRepo) + + mockComplaintRepo.On("Update", complaint).Return(entities.Complaint{}, constants.ErrInternalServerError) + + mockUsecase := NewComplaintUseCase(mockComplaintRepo, mockComplaintFileRepo) + + result, err := mockUsecase.Update(complaint) + assert.Error(t, constants.ErrInternalServerError, err) + assert.Equal(t, entities.Complaint{}, result) + + mockComplaintRepo.AssertExpectations(t) + mockComplaintFileRepo.AssertExpectations(t) + }) +} + +func TestUpdateStatus(t *testing.T) { + t.Run("success", func(t *testing.T) { + mockComplaintRepo := new(MockComplaintRepo) + mockComplaintFileRepo := new(MockComplaintFileRepo) + + mockComplaintRepo.On("UpdateStatus", "1", "Selesai").Return(nil) + + mockUsecase := NewComplaintUseCase(mockComplaintRepo, mockComplaintFileRepo) + + err := mockUsecase.UpdateStatus("1", "Selesai") + assert.NoError(t, err) + + mockComplaintRepo.AssertExpectations(t) + mockComplaintFileRepo.AssertExpectations(t) + }) + + t.Run("failed internal server error", func(t *testing.T) { + mockComplaintRepo := new(MockComplaintRepo) + mockComplaintFileRepo := new(MockComplaintFileRepo) + + mockComplaintRepo.On("UpdateStatus", "1", "Selesai").Return(constants.ErrInternalServerError) + + mockUsecase := NewComplaintUseCase(mockComplaintRepo, mockComplaintFileRepo) + + err := mockUsecase.UpdateStatus("1", "Selesai") + assert.Error(t, constants.ErrInternalServerError, err) + + mockComplaintRepo.AssertExpectations(t) + mockComplaintFileRepo.AssertExpectations(t) + }) + + t.Run("failed invalid status", func(t *testing.T) { + mockComplaintRepo := new(MockComplaintRepo) + mockComplaintFileRepo := new(MockComplaintFileRepo) + + mockUsecase := NewComplaintUseCase(mockComplaintRepo, mockComplaintFileRepo) + + err := mockUsecase.UpdateStatus("1", "Invalid") + assert.Error(t, constants.ErrInvalidStatus, err) + + mockComplaintRepo.AssertExpectations(t) + mockComplaintFileRepo.AssertExpectations(t) + }) + + t.Run("failed empty id", func(t *testing.T) { + mockComplaintRepo := new(MockComplaintRepo) + mockComplaintFileRepo := new(MockComplaintFileRepo) + + mockUsecase := NewComplaintUseCase(mockComplaintRepo, mockComplaintFileRepo) + + err := mockUsecase.UpdateStatus("", "Selesai") + assert.Error(t, constants.ErrIDMustBeFilled, err) + + mockComplaintRepo.AssertExpectations(t) + mockComplaintFileRepo.AssertExpectations(t) + }) +} + +// Mock utility function +func GetRowsFromExcel(file *multipart.FileHeader) ([][]string, error) { + // This is just a placeholder. You should mock this function as needed. + return nil, nil +} + +func TestImport(t *testing.T) { + t.Run("success", func(t *testing.T) { + mockComplaintRepo := new(MockComplaintRepo) + mockComplaintFileRepo := new(MockComplaintFileRepo) + + complaintUseCase := NewComplaintUseCase(mockComplaintRepo, mockComplaintFileRepo) + + fileHeader := &multipart.FileHeader{} + + rows := [][]string{ + {"UserID", "CategoryID", "RegencyID", "Address", "Description", "Status", "Type", "Date", "Files"}, + {"1", "2", "Regency1", "Address1", "Description1", "Verifikasi", "Type1", "05-05-2024", "file1.jpg,file2.jpg"}, + {"1", "2", "Regency1", "Address1", "Description1", "On Progress", "Type1", "05-05-2024", "file1.jpg"}, + {"1", "2", "Regency1", "Address1", "Description1", "Selesai", "Type1", "05-05-2024", "file1.jpg,file2.jpg"}, + {"1", "2", "Regency1", "Address1", "Description1", "Ditolak", "Type1", "05-05-2024", "file1.jpg"}, + {"1", "2", "Regency1", "Address1", "Description1", "Pending", "Type1", "05-05-2024", "file1.jpg"}, + } + + complaintUseCase.getRowsFromExcel = func(file *multipart.FileHeader) ([][]string, error) { + return rows, nil + } + + mockComplaintRepo.On("Import", mock.Anything).Return(nil) + + err := complaintUseCase.Import(fileHeader) + assert.NoError(t, err) + + mockComplaintRepo.AssertExpectations(t) + }) + + t.Run("failed getting rows from excel", func(t *testing.T) { + mockComplaintRepo := new(MockComplaintRepo) + mockComplaintFileRepo := new(MockComplaintFileRepo) + + complaintUseCase := NewComplaintUseCase(mockComplaintRepo, mockComplaintFileRepo) + + fileHeader := &multipart.FileHeader{} + complaintUseCase.getRowsFromExcel = func(file *multipart.FileHeader) ([][]string, error) { + return nil, errors.New("failed to read excel") + } + + err := complaintUseCase.Import(fileHeader) + assert.Error(t, err) + assert.Equal(t, "failed to read excel", err.Error()) + }) + + t.Run("failed importing complaints", func(t *testing.T) { + mockComplaintRepo := new(MockComplaintRepo) + mockComplaintFileRepo := new(MockComplaintFileRepo) + + complaintUseCase := NewComplaintUseCase(mockComplaintRepo, mockComplaintFileRepo) + + fileHeader := &multipart.FileHeader{} + + rows := [][]string{ + {"UserID", "CategoryID", "RegencyID", "Address", "Description", "Status", "Type", "Date", "Files"}, + {"1", "2", "Regency1", "Address1", "Description1", "Verifikasi", "Type1", "05-05-2024", "file1.jpg"}, + } + + complaintUseCase.getRowsFromExcel = func(file *multipart.FileHeader) ([][]string, error) { + return rows, nil + } + + mockComplaintRepo.On("Import", mock.Anything).Return(errors.New("import failed")) + + err := complaintUseCase.Import(fileHeader) + assert.Error(t, err) + assert.Equal(t, "import failed", err.Error()) + + mockComplaintRepo.AssertExpectations(t) + }) + + t.Run("failed not enough columns", func(t *testing.T) { + mockComplaintRepo := new(MockComplaintRepo) + mockComplaintFileRepo := new(MockComplaintFileRepo) + + complaintUseCase := NewComplaintUseCase(mockComplaintRepo, mockComplaintFileRepo) + + fileHeader := &multipart.FileHeader{} + + rows := [][]string{ + {"UserID", "CategoryID", "RegencyID", "Address", "Description"}, + {"1", "2", "Regency1", "Address1"}, + } + + complaintUseCase.getRowsFromExcel = func(file *multipart.FileHeader) ([][]string, error) { + return rows, nil + } + + err := complaintUseCase.Import(fileHeader) + assert.Error(t, err) + assert.Equal(t, constants.ErrColumnsDoesntMatch, err) + }) + + t.Run("failed invalid user id", func(t *testing.T) { + mockComplaintRepo := new(MockComplaintRepo) + mockComplaintFileRepo := new(MockComplaintFileRepo) + + complaintUseCase := NewComplaintUseCase(mockComplaintRepo, mockComplaintFileRepo) + + fileHeader := &multipart.FileHeader{} + + rows := [][]string{ + {"UserID", "CategoryID", "RegencyID", "Address", "Description", "Status", "Type", "Date", "Files"}, + {"a", "2", "Regency1", "Address1", "Description1", "Verifikasi", "Type1", "05-05-2024", "file1.jpg"}, + } + + complaintUseCase.getRowsFromExcel = func(file *multipart.FileHeader) ([][]string, error) { + return rows, nil + } + + err := complaintUseCase.Import(fileHeader) + assert.Error(t, err) + assert.Equal(t, constants.ErrInvalidIDFormat, err) + + mockComplaintRepo.AssertExpectations(t) + }) + + t.Run("failed invalid category id", func(t *testing.T) { + mockComplaintRepo := new(MockComplaintRepo) + mockComplaintFileRepo := new(MockComplaintFileRepo) + + complaintUseCase := NewComplaintUseCase(mockComplaintRepo, mockComplaintFileRepo) + + fileHeader := &multipart.FileHeader{} + + rows := [][]string{ + {"UserID", "CategoryID", "RegencyID", "Address", "Description", "Status", "Type", "Date", "Files"}, + {"1", "a", "Regency1", "Address1", "Description1", "Verifikasi", "Type1", "05-05-2024", "file1.jpg"}, + } + + complaintUseCase.getRowsFromExcel = func(file *multipart.FileHeader) ([][]string, error) { + return rows, nil + } + + err := complaintUseCase.Import(fileHeader) + assert.Error(t, err) + assert.Equal(t, constants.ErrInvalidCategoryIDFormat, err) + + mockComplaintRepo.AssertExpectations(t) + }) +} + +func TestIncreaseTotalLikes(t *testing.T) { + t.Run("success", func(t *testing.T) { + mockComplaintRepo := new(MockComplaintRepo) + mockComplaintFileRepo := new(MockComplaintFileRepo) + + complaintUseCase := NewComplaintUseCase(mockComplaintRepo, mockComplaintFileRepo) + + mockComplaintRepo.On("IncreaseTotalLikes", "1").Return(nil) + + err := complaintUseCase.IncreaseTotalLikes("1") + assert.NoError(t, err) + + mockComplaintRepo.AssertExpectations(t) + }) + + t.Run("failed internal server error", func(t *testing.T) { + mockComplaintRepo := new(MockComplaintRepo) + mockComplaintFileRepo := new(MockComplaintFileRepo) + + complaintUseCase := NewComplaintUseCase(mockComplaintRepo, mockComplaintFileRepo) + + mockComplaintRepo.On("IncreaseTotalLikes", "1").Return(constants.ErrInternalServerError) + + err := complaintUseCase.IncreaseTotalLikes("1") + assert.Error(t, constants.ErrInternalServerError, err) + + mockComplaintRepo.AssertExpectations(t) + }) +} + +func TestDecreaseTotalLikes(t *testing.T) { + t.Run("success", func(t *testing.T) { + mockComplaintRepo := new(MockComplaintRepo) + mockComplaintFileRepo := new(MockComplaintFileRepo) + + complaintUseCase := NewComplaintUseCase(mockComplaintRepo, mockComplaintFileRepo) + + mockComplaintRepo.On("DecreaseTotalLikes", "1").Return(nil) + + err := complaintUseCase.DecreaseTotalLikes("1") + assert.NoError(t, err) + + mockComplaintRepo.AssertExpectations(t) + }) + + t.Run("failed internal server error", func(t *testing.T) { + mockComplaintRepo := new(MockComplaintRepo) + mockComplaintFileRepo := new(MockComplaintFileRepo) + + complaintUseCase := NewComplaintUseCase(mockComplaintRepo, mockComplaintFileRepo) + + mockComplaintRepo.On("DecreaseTotalLikes", "1").Return(constants.ErrInternalServerError) + + err := complaintUseCase.DecreaseTotalLikes("1") + assert.Error(t, constants.ErrInternalServerError, err) + + mockComplaintRepo.AssertExpectations(t) + }) +} + +func TestGetComplaintIDsByUserID(t *testing.T) { + t.Run("success empty", func(t *testing.T) { + mockComplaintRepo := new(MockComplaintRepo) + mockComplaintFileRepo := new(MockComplaintFileRepo) + + complaintUseCase := NewComplaintUseCase(mockComplaintRepo, mockComplaintFileRepo) + + mockComplaintRepo.On("GetComplaintIDsByUserID", 1).Return([]string{}, nil) + + result, err := complaintUseCase.GetComplaintIDsByUserID(1) + assert.NoError(t, err) + assert.Equal(t, []string{}, result) + + mockComplaintRepo.AssertExpectations(t) + }) + + t.Run("success not empty", func(t *testing.T) { + mockComplaintRepo := new(MockComplaintRepo) + mockComplaintFileRepo := new(MockComplaintFileRepo) + + complaintUseCase := NewComplaintUseCase(mockComplaintRepo, mockComplaintFileRepo) + + mockComplaintRepo.On("GetComplaintIDsByUserID", 1).Return([]string{"1", "2"}, nil) + + result, err := complaintUseCase.GetComplaintIDsByUserID(1) + assert.NoError(t, err) + assert.Equal(t, []string{"1", "2"}, result) + + mockComplaintRepo.AssertExpectations(t) + }) + + t.Run("failed internal server error", func(t *testing.T) { + mockComplaintRepo := new(MockComplaintRepo) + mockComplaintFileRepo := new(MockComplaintFileRepo) + + complaintUseCase := NewComplaintUseCase(mockComplaintRepo, mockComplaintFileRepo) + + mockComplaintRepo.On("GetComplaintIDsByUserID", 1).Return([]string(nil), constants.ErrInternalServerError) + + result, err := complaintUseCase.GetComplaintIDsByUserID(1) + assert.Error(t, constants.ErrInternalServerError, err) + assert.Equal(t, []string(nil), result) + + mockComplaintRepo.AssertExpectations(t) + }) +} diff --git a/usecases/complaint_activity/complaint_activity_test.go b/usecases/complaint_activity/complaint_activity_test.go new file mode 100644 index 0000000..cebbca8 --- /dev/null +++ b/usecases/complaint_activity/complaint_activity_test.go @@ -0,0 +1,202 @@ +package complaint_activity + +import ( + "e-complaint-api/entities" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +type ComplaintActivityRepository struct { + mock.Mock +} + +func (m *ComplaintActivityRepository) GetByComplaintIDs(complaintIDs []string, activityType string) ([]entities.ComplaintActivity, error) { + args := m.Called(complaintIDs, activityType) + return args.Get(0).([]entities.ComplaintActivity), args.Error(1) +} + +func (m *ComplaintActivityRepository) Create(complaintActivity *entities.ComplaintActivity) error { + args := m.Called(complaintActivity) + return args.Error(0) +} + +func (m *ComplaintActivityRepository) Delete(complaintActivity entities.ComplaintActivity) error { + args := m.Called(complaintActivity) + return args.Error(0) +} + +func (m *ComplaintActivityRepository) Update(complaintActivity entities.ComplaintActivity) error { + args := m.Called(complaintActivity) + return args.Error(0) +} + +func TestGetByComplaintIDs(t *testing.T) { + t.Run("success", func(t *testing.T) { + mockRepo := new(ComplaintActivityRepository) + complaintActivity := []entities.ComplaintActivity{ + { + ID: 1, + ComplaintID: "1", + LikeID: nil, + DiscussionID: nil, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }, + } + + mockRepo.On("GetByComplaintIDs", []string{"1"}, "").Return(complaintActivity, nil) + + u := NewComplaintActivityUseCase(mockRepo) + res, err := u.GetByComplaintIDs([]string{"1"}, "") + assert.NoError(t, err) + assert.Equal(t, complaintActivity, res) + }) + + t.Run("error", func(t *testing.T) { + mockRepo := new(ComplaintActivityRepository) + mockRepo.On("GetByComplaintIDs", []string{"1"}, "").Return(([]entities.ComplaintActivity)(nil), assert.AnError) + + u := NewComplaintActivityUseCase(mockRepo) + res, err := u.GetByComplaintIDs([]string{"1"}, "") + assert.Error(t, err) + assert.Nil(t, res) + }) +} + +func TestCreate(t *testing.T) { + t.Run("success", func(t *testing.T) { + mockRepo := new(ComplaintActivityRepository) + complaintActivity := &entities.ComplaintActivity{ + ID: 1, + ComplaintID: "1", + LikeID: nil, + DiscussionID: nil, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + + mockRepo.On("Create", complaintActivity).Return(nil) + + u := NewComplaintActivityUseCase(mockRepo) + res, err := u.Create(complaintActivity) + assert.NoError(t, err) + assert.Equal(t, *complaintActivity, res) + }) + + t.Run("error", func(t *testing.T) { + mockRepo := new(ComplaintActivityRepository) + complaintActivity := &entities.ComplaintActivity{ + ID: 1, + ComplaintID: "1", + LikeID: nil, + DiscussionID: nil, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + + mockRepo.On("Create", complaintActivity).Return(assert.AnError) + + u := NewComplaintActivityUseCase(mockRepo) + res, err := u.Create(complaintActivity) + assert.Error(t, err) + assert.Equal(t, entities.ComplaintActivity{}, res) + }) + + t.Run("error", func(t *testing.T) { + mockRepo := new(ComplaintActivityRepository) + complaintActivity := &entities.ComplaintActivity{ + ID: 1, + ComplaintID: "1", + LikeID: nil, + DiscussionID: nil, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + + mockRepo.On("Create", complaintActivity).Return(assert.AnError) + + u := NewComplaintActivityUseCase(mockRepo) + res, err := u.Create(complaintActivity) + assert.Error(t, err) + assert.Equal(t, entities.ComplaintActivity{}, res) + }) +} + +func TestDelete(t *testing.T) { + t.Run("success", func(t *testing.T) { + mockRepo := new(ComplaintActivityRepository) + complaintActivity := entities.ComplaintActivity{ + ID: 1, + ComplaintID: "1", + LikeID: nil, + DiscussionID: nil, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + + mockRepo.On("Delete", complaintActivity).Return(nil) + + u := NewComplaintActivityUseCase(mockRepo) + err := u.Delete(complaintActivity) + assert.NoError(t, err) + }) + + t.Run("error", func(t *testing.T) { + mockRepo := new(ComplaintActivityRepository) + complaintActivity := entities.ComplaintActivity{ + ID: 1, + ComplaintID: "1", + LikeID: nil, + DiscussionID: nil, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + + mockRepo.On("Delete", complaintActivity).Return(assert.AnError) + + u := NewComplaintActivityUseCase(mockRepo) + err := u.Delete(complaintActivity) + assert.Error(t, err) + }) +} + +func TestUpdate(t *testing.T) { + t.Run("success", func(t *testing.T) { + mockRepo := new(ComplaintActivityRepository) + complaintActivity := entities.ComplaintActivity{ + ID: 1, + ComplaintID: "1", + LikeID: nil, + DiscussionID: nil, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + + mockRepo.On("Update", complaintActivity).Return(nil) + + u := NewComplaintActivityUseCase(mockRepo) + err := u.Update(complaintActivity) + assert.NoError(t, err) + }) + + t.Run("error", func(t *testing.T) { + mockRepo := new(ComplaintActivityRepository) + complaintActivity := entities.ComplaintActivity{ + ID: 1, + ComplaintID: "1", + LikeID: nil, + DiscussionID: nil, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + + mockRepo.On("Update", complaintActivity).Return(assert.AnError) + + u := NewComplaintActivityUseCase(mockRepo) + err := u.Update(complaintActivity) + assert.Error(t, err) + }) +} diff --git a/usecases/complaint_file/complaint_file_test.go b/usecases/complaint_file/complaint_file_test.go new file mode 100644 index 0000000..196fa9b --- /dev/null +++ b/usecases/complaint_file/complaint_file_test.go @@ -0,0 +1,121 @@ +package complaint_file + +import ( + "e-complaint-api/entities" + "errors" + "mime/multipart" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +type MockComplaintFileRepository struct { + mock.Mock +} + +func (m *MockComplaintFileRepository) Create(complaintFiles []*entities.ComplaintFile) error { + args := m.Called(complaintFiles) + return args.Error(0) +} + +func (m *MockComplaintFileRepository) DeleteByComplaintID(complaintID string) error { + args := m.Called(complaintID) + return args.Error(0) +} + +type MockComplaintFileGCSAPI struct { + mock.Mock +} + +func (m *MockComplaintFileGCSAPI) Upload(files []*multipart.FileHeader) ([]string, error) { + args := m.Called(files) + return args.Get(0).([]string), args.Error(1) +} + +func (m *MockComplaintFileGCSAPI) Delete(paths []string) error { + args := m.Called(paths) + return args.Error(0) +} + +func TestCreate(t *testing.T) { + t.Run("success", func(t *testing.T) { + repo := new(MockComplaintFileRepository) + gcs_api := new(MockComplaintFileGCSAPI) + usecase := NewComplaintFileUseCase(repo, gcs_api) + + files := []*multipart.FileHeader{} + complaintID := "complaint_id" + + repo.On("Create", mock.Anything).Return(nil) + gcs_api.On("Upload", files).Return([]string{"path"}, nil) + + result, err := usecase.Create(files, complaintID) + + assert.NoError(t, err) + assert.NotEmpty(t, result) + }) + + t.Run("failed to upload", func(t *testing.T) { + repo := new(MockComplaintFileRepository) + gcs_api := new(MockComplaintFileGCSAPI) + usecase := NewComplaintFileUseCase(repo, gcs_api) + + files := []*multipart.FileHeader{} + complaintID := "complaint_id" + + gcs_api.On("Upload", files).Return([]string{}, errors.New("failed to upload")) + + result, err := usecase.Create(files, complaintID) + + assert.Error(t, err) + assert.Empty(t, result) + }) + + t.Run("failed to create", func(t *testing.T) { + repo := new(MockComplaintFileRepository) + gcs_api := new(MockComplaintFileGCSAPI) + usecase := NewComplaintFileUseCase(repo, gcs_api) + + files := []*multipart.FileHeader{} + complaintID := "complaint_id" + + repo.On("Create", mock.Anything).Return(errors.New("failed to create")) + gcs_api.On("Upload", files).Return([]string{"path"}, nil) + + result, err := usecase.Create(files, complaintID) + + assert.Error(t, err) + assert.Empty(t, result) + }) +} + +func TestDeleteByComplaintID(t *testing.T) { + t.Run("success", func(t *testing.T) { + repo := new(MockComplaintFileRepository) + gcs_api := new(MockComplaintFileGCSAPI) + usecase := NewComplaintFileUseCase(repo, gcs_api) + + complaintID := "complaint_id" + + repo.On("DeleteByComplaintID", complaintID).Return(nil) + + err := usecase.DeleteByComplaintID(complaintID) + + assert.NoError(t, err) + }) + + t.Run("failed to delete", func(t *testing.T) { + repo := new(MockComplaintFileRepository) + gcs_api := new(MockComplaintFileGCSAPI) + usecase := NewComplaintFileUseCase(repo, gcs_api) + + complaintID := "complaint_id" + + repo.On("DeleteByComplaintID", complaintID).Return(errors.New("failed to delete")) + + err := usecase.DeleteByComplaintID(complaintID) + + assert.Error(t, err) + }) +} diff --git a/usecases/complaint_like/complaint_like_test.go b/usecases/complaint_like/complaint_like_test.go new file mode 100644 index 0000000..e8c88d5 --- /dev/null +++ b/usecases/complaint_like/complaint_like_test.go @@ -0,0 +1,127 @@ +package complaint_like + +import ( + "e-complaint-api/entities" + "errors" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "testing" +) + +type MockComplaintLike struct { + mock.Mock +} + +func (m MockComplaintLike) Unlike(complaintLike *entities.ComplaintLike) error { + args := m.Called(complaintLike) + return args.Error(0) + +} + +func (m MockComplaintLike) Likes(complaintLike *entities.ComplaintLike) error { + args := m.Called(complaintLike) + return args.Error(0) + +} + +func (m MockComplaintLike) FindByUserAndComplaint(userID int, complaintID string) (*entities.ComplaintLike, error) { + args := m.Called(userID, complaintID) + if args.Get(0) == nil { + return nil, args.Error(1) + } + return args.Get(0).(*entities.ComplaintLike), args.Error(1) +} + +func TestComplaintLikeUseCase_ToggleLike(t *testing.T) { + t.Run("success - like", func(t *testing.T) { + mockRepo := new(MockComplaintLike) + useCase := NewComplaintLikeUseCase(mockRepo) + + complaintLike := &entities.ComplaintLike{ + UserID: 1, + ComplaintID: "C-123j9ak280", + } + + mockRepo.On("FindByUserAndComplaint", complaintLike.UserID, complaintLike.ComplaintID).Return(nil, nil) + mockRepo.On("Likes", complaintLike).Return(nil) + + status, err := useCase.ToggleLike(complaintLike) + assert.Nil(t, err) + assert.Equal(t, "liked", status) + }) + + t.Run("success - unlike", func(t *testing.T) { + mockRepo := new(MockComplaintLike) + useCase := NewComplaintLikeUseCase(mockRepo) + + complaintLike := &entities.ComplaintLike{ + UserID: 1, + ComplaintID: "1", + } + + existingComplaintLike := &entities.ComplaintLike{ + UserID: 1, + ComplaintID: "C-123j9ak280", + } + + mockRepo.On("FindByUserAndComplaint", complaintLike.UserID, complaintLike.ComplaintID).Return(existingComplaintLike, nil) + mockRepo.On("Unlike", existingComplaintLike).Return(nil) + + status, err := useCase.ToggleLike(complaintLike) + assert.Nil(t, err) + assert.Equal(t, "unliked", status) + }) + + t.Run("error - FindByUserAndComplaint", func(t *testing.T) { + mockRepo := new(MockComplaintLike) + useCase := NewComplaintLikeUseCase(mockRepo) + + complaintLike := &entities.ComplaintLike{ + UserID: 1, + ComplaintID: "C-123j9ak280", + } + + mockRepo.On("FindByUserAndComplaint", complaintLike.UserID, complaintLike.ComplaintID).Return(nil, errors.New("error")) + + _, err := useCase.ToggleLike(complaintLike) + assert.NotNil(t, err) + }) + + t.Run("error - Likes", func(t *testing.T) { + mockRepo := new(MockComplaintLike) + useCase := NewComplaintLikeUseCase(mockRepo) + + complaintLike := &entities.ComplaintLike{ + UserID: 1, + ComplaintID: "C-123j9ak280", + } + + mockRepo.On("FindByUserAndComplaint", complaintLike.UserID, complaintLike.ComplaintID).Return(nil, nil) + mockRepo.On("Likes", complaintLike).Return(errors.New("error")) + + _, err := useCase.ToggleLike(complaintLike) + assert.NotNil(t, err) + }) + + t.Run("error - Unlike", func(t *testing.T) { + mockRepo := new(MockComplaintLike) + useCase := NewComplaintLikeUseCase(mockRepo) + + complaintLike := &entities.ComplaintLike{ + UserID: 1, + ComplaintID: "C-123j9ak280", + } + + existingComplaintLike := &entities.ComplaintLike{ + UserID: 1, + ComplaintID: "C-123j9ak280", + } + + mockRepo.On("FindByUserAndComplaint", complaintLike.UserID, complaintLike.ComplaintID).Return(existingComplaintLike, nil) + mockRepo.On("Unlike", existingComplaintLike).Return(errors.New("error")) + + _, err := useCase.ToggleLike(complaintLike) + assert.NotNil(t, err) + }) + +} diff --git a/usecases/complaint_process/complaint_process.go b/usecases/complaint_process/complaint_process.go index a57068a..e1fdddd 100644 --- a/usecases/complaint_process/complaint_process.go +++ b/usecases/complaint_process/complaint_process.go @@ -29,13 +29,13 @@ func (u *ComplaintProcessUseCase) Create(complaintProcess *entities.ComplaintPro status, err := u.complaintRepository.GetStatus(complaintProcess.ComplaintID) if err != nil { - return entities.ComplaintProcess{}, err + return entities.ComplaintProcess{}, constants.ErrInternalServerError } if complaintProcess.Status == "Pending" { if status == "On Progress" { return entities.ComplaintProcess{}, constants.ErrComplaintNotVerified - } else if complaintProcess.Status == "Selesai" { + } else if status == "Selesai" { return entities.ComplaintProcess{}, constants.ErrComplaintNotVerified } } else if complaintProcess.Status == "Verifikasi" { diff --git a/usecases/complaint_process/complaint_process_test.go b/usecases/complaint_process/complaint_process_test.go new file mode 100644 index 0000000..ee04d7d --- /dev/null +++ b/usecases/complaint_process/complaint_process_test.go @@ -0,0 +1,768 @@ +package complaint_process + +import ( + "e-complaint-api/constants" + "e-complaint-api/entities" + "errors" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +type MockComplaintProcess struct { + mock.Mock +} + +func (m *MockComplaintProcess) Create(complaintProcesses *entities.ComplaintProcess) error { + args := m.Called(complaintProcesses) + return args.Error(0) +} + +func (m *MockComplaintProcess) GetByComplaintID(complaintID string) ([]entities.ComplaintProcess, error) { + args := m.Called(complaintID) + result := args.Get(0) + return result.([]entities.ComplaintProcess), args.Error(1) +} + +func (m *MockComplaintProcess) Update(complaintProcesses *entities.ComplaintProcess) error { + args := m.Called(complaintProcesses) + return args.Error(0) +} + +func (m *MockComplaintProcess) Delete(complaintID string, complaintProcessID int) (string, error) { + args := m.Called(complaintID, complaintProcessID) + return args.String(0), args.Error(1) +} + +type MockComplaint struct { + mock.Mock +} + +func (m *MockComplaint) GetByUserID(userId int) ([]entities.Complaint, error) { + args := m.Called(userId) + result := args.Get(0) + return result.([]entities.Complaint), args.Error(1) + +} + +func (m *MockComplaint) Create(complaint *entities.Complaint) error { + args := m.Called(complaint) + return args.Error(0) + +} + +func (m *MockComplaint) Delete(id string, userId int) error { + args := m.Called(id, userId) + return args.Error(0) + +} + +func (m *MockComplaint) AdminDelete(id string) error { + args := m.Called(id) + return args.Error(0) + +} + +func (m *MockComplaint) Update(complaint entities.Complaint) (entities.Complaint, error) { + args := m.Called(complaint) + result := args.Get(0) + return result.(entities.Complaint), args.Error(1) + +} + +func (m *MockComplaint) UpdateStatus(id string, status string) error { + args := m.Called(id, status) + return args.Error(0) + +} + +func (m *MockComplaint) GetStatus(id string) (string, error) { + args := m.Called(id) + return args.String(0), args.Error(1) + +} + +func (m *MockComplaint) Import(complaints []entities.Complaint) error { + args := m.Called(complaints) + return args.Error(0) + +} + +func (m *MockComplaint) IncreaseTotalLikes(id string) error { + args := m.Called(id) + return args.Error(0) + +} + +func (m *MockComplaint) DecreaseTotalLikes(id string) error { + args := m.Called(id) + return args.Error(0) +} + +func (m *MockComplaint) GetComplaintIDsByUserID(userID int) ([]string, error) { + args := m.Called(userID) + result := args.Get(0) + return result.([]string), args.Error(1) + +} + +func (m *MockComplaint) GetPaginated(limit int, page int, search string, filter map[string]interface{}, sortBy string, sortType string) ([]entities.Complaint, error) { + args := m.Called(limit, page, search, filter, sortBy, sortType) + result := args.Get(0) + return result.([]entities.Complaint), args.Error(1) +} + +func (m *MockComplaint) GetMetaData(limit int, page int, search string, filter map[string]interface{}) (entities.Metadata, error) { + args := m.Called(limit, page, search, filter) + result := args.Get(0) + return result.(entities.Metadata), args.Error(1) +} + +func (m *MockComplaint) GetByID(id string) (entities.Complaint, error) { + args := m.Called(id) + result := args.Get(0) + return result.(entities.Complaint), args.Error(1) +} + +func TestCreate(t *testing.T) { + t.Run("success", func(t *testing.T) { + mockComplaintProcessRepo := new(MockComplaintProcess) + mockComplaintRepo := new(MockComplaint) + usecase := NewComplaintProcessUseCase(mockComplaintProcessRepo, mockComplaintRepo) + dummyComplaintProcess := &entities.ComplaintProcess{ + Message: "Test Message", + Status: "Pending", + ComplaintID: "123", + } + + mockComplaintRepo.On("GetStatus", mock.Anything).Return("Pending", nil) + mockComplaintProcessRepo.On("Create", mock.Anything).Return(nil) + + result, err := usecase.Create(dummyComplaintProcess) + + assert.NoError(t, err) + assert.NotNil(t, result) + }) + + t.Run("internal server error", func(t *testing.T) { + mockComplaintProcessRepo := new(MockComplaintProcess) + mockComplaintRepo := new(MockComplaint) + usecase := NewComplaintProcessUseCase(mockComplaintProcessRepo, mockComplaintRepo) + dummyComplaintProcess := &entities.ComplaintProcess{ + Message: "Test Message", + Status: "Pending", + ComplaintID: "123", + } + + mockComplaintRepo.On("GetStatus", mock.Anything).Return("", constants.ErrInternalServerError) + mockComplaintProcessRepo.On("Create", mock.Anything).Return(constants.ErrInternalServerError) + result, err := usecase.Create(dummyComplaintProcess) + assert.Error(t, err) + assert.Equal(t, entities.ComplaintProcess{}, result) + assert.Equal(t, constants.ErrInternalServerError, err) + }) + + t.Run("error when message is empty", func(t *testing.T) { + mockComplaintProcessRepo := new(MockComplaintProcess) + mockComplaintRepo := new(MockComplaint) + usecase := NewComplaintProcessUseCase(mockComplaintProcessRepo, mockComplaintRepo) + dummyComplaintProcess := &entities.ComplaintProcess{ + Message: "", + Status: "Pending", + ComplaintID: "123", + } + + result, err := usecase.Create(dummyComplaintProcess) + + assert.Error(t, err) + assert.Equal(t, entities.ComplaintProcess{}, result) + assert.Equal(t, constants.ErrAllFieldsMustBeFilled, err) + }) + + t.Run("error when status is invalid", func(t *testing.T) { + mockComplaintProcessRepo := new(MockComplaintProcess) + mockComplaintRepo := new(MockComplaint) + usecase := NewComplaintProcessUseCase(mockComplaintProcessRepo, mockComplaintRepo) + dummyComplaintProcess := &entities.ComplaintProcess{ + Message: "Test Message", + Status: "Invalid", + ComplaintID: "123", + } + + result, err := usecase.Create(dummyComplaintProcess) + + assert.Error(t, err) + assert.Equal(t, entities.ComplaintProcess{}, result) + assert.Equal(t, constants.ErrInvalidStatus, err) + }) + + t.Run("error when status is Pending and complaint status is On Progress", func(t *testing.T) { + mockComplaintProcessRepo := new(MockComplaintProcess) + mockComplaintRepo := new(MockComplaint) + usecase := NewComplaintProcessUseCase(mockComplaintProcessRepo, mockComplaintRepo) + dummyComplaintProcess := &entities.ComplaintProcess{ + Message: "Test Message", + Status: "Pending", + ComplaintID: "123", + } + + mockComplaintRepo.On("GetStatus", mock.Anything).Return("On Progress", nil) + + result, err := usecase.Create(dummyComplaintProcess) + + assert.Error(t, err) + assert.Equal(t, entities.ComplaintProcess{}, result) + assert.Equal(t, constants.ErrComplaintNotVerified, err) + }) + + t.Run("error when status is Pending and complaint status is Selesai", func(t *testing.T) { + mockComplaintProcessRepo := new(MockComplaintProcess) + mockComplaintRepo := new(MockComplaint) + usecase := NewComplaintProcessUseCase(mockComplaintProcessRepo, mockComplaintRepo) + dummyComplaintProcess := &entities.ComplaintProcess{ + Message: "Test Message", + Status: "Pending", + ComplaintID: "123", + } + + mockComplaintRepo.On("GetStatus", mock.Anything).Return("Selesai", nil) + + result, err := usecase.Create(dummyComplaintProcess) + + assert.Error(t, err) + assert.Equal(t, entities.ComplaintProcess{}, result) + assert.Equal(t, constants.ErrComplaintNotVerified, err) + }) + + t.Run("error when complaint not found in repository", func(t *testing.T) { + mockComplaintProcessRepo := new(MockComplaintProcess) + mockComplaintRepo := new(MockComplaint) + usecase := NewComplaintProcessUseCase(mockComplaintProcessRepo, mockComplaintRepo) + dummyComplaintProcess := &entities.ComplaintProcess{ + Message: "Test Message", + Status: "Pending", + ComplaintID: "123", + } + + mockComplaintRepo.On("GetStatus", mock.Anything).Return("Pending", nil) + mockComplaintProcessRepo.On("Create", mock.Anything).Return(errors.New("REFERENCES `complaints` (`id`)")) + + result, err := usecase.Create(dummyComplaintProcess) + + assert.Error(t, err) + assert.Equal(t, entities.ComplaintProcess{}, result) + assert.Equal(t, constants.ErrComplaintNotFound, err) + }) + + t.Run("error when internal server error occurs in repository", func(t *testing.T) { + mockComplaintProcessRepo := new(MockComplaintProcess) + mockComplaintRepo := new(MockComplaint) + usecase := NewComplaintProcessUseCase(mockComplaintProcessRepo, mockComplaintRepo) + dummyComplaintProcess := &entities.ComplaintProcess{ + Message: "Test Message", + Status: "Pending", + ComplaintID: "123", + } + + mockComplaintRepo.On("GetStatus", mock.Anything).Return("Pending", nil) + mockComplaintProcessRepo.On("Create", mock.Anything).Return(errors.New("some other error")) + + result, err := usecase.Create(dummyComplaintProcess) + + assert.Error(t, err) + assert.Equal(t, entities.ComplaintProcess{}, result) + assert.Equal(t, constants.ErrInternalServerError, err) + }) + + t.Run("error when status is Verifikasi and complaint status is Verifikasi", func(t *testing.T) { + mockComplaintProcessRepo := new(MockComplaintProcess) + mockComplaintRepo := new(MockComplaint) + usecase := NewComplaintProcessUseCase(mockComplaintProcessRepo, mockComplaintRepo) + dummyComplaintProcess := &entities.ComplaintProcess{ + Message: "Test Message", + Status: "Verifikasi", + ComplaintID: "123", + } + + mockComplaintRepo.On("GetStatus", mock.Anything).Return("Verifikasi", nil) + + result, err := usecase.Create(dummyComplaintProcess) + + assert.Error(t, err) + assert.Equal(t, entities.ComplaintProcess{}, result) + assert.Equal(t, constants.ErrComplaintAlreadyVerified, err) + }) + + t.Run("error when status is Verifikasi and complaint status is Ditolak", func(t *testing.T) { + mockComplaintProcessRepo := new(MockComplaintProcess) + mockComplaintRepo := new(MockComplaint) + usecase := NewComplaintProcessUseCase(mockComplaintProcessRepo, mockComplaintRepo) + dummyComplaintProcess := &entities.ComplaintProcess{ + Message: "Test Message", + Status: "Verifikasi", + ComplaintID: "123", + } + + mockComplaintRepo.On("GetStatus", mock.Anything).Return("Ditolak", nil) + + result, err := usecase.Create(dummyComplaintProcess) + + assert.Error(t, err) + assert.Equal(t, entities.ComplaintProcess{}, result) + assert.Equal(t, constants.ErrComplaintAlreadyRejected, err) + }) + + t.Run("error when status is Verifikasi and complaint status is Selesai", func(t *testing.T) { + mockComplaintProcessRepo := new(MockComplaintProcess) + mockComplaintRepo := new(MockComplaint) + usecase := NewComplaintProcessUseCase(mockComplaintProcessRepo, mockComplaintRepo) + dummyComplaintProcess := &entities.ComplaintProcess{ + Message: "Test Message", + Status: "Verifikasi", + ComplaintID: "123", + } + + mockComplaintRepo.On("GetStatus", mock.Anything).Return("Selesai", nil) + + result, err := usecase.Create(dummyComplaintProcess) + + assert.Error(t, err) + assert.Equal(t, entities.ComplaintProcess{}, result) + assert.Equal(t, constants.ErrComplaintAlreadyFinished, err) + }) + + t.Run("error when status is Verifikasi and complaint status is On Progress", func(t *testing.T) { + mockComplaintProcessRepo := new(MockComplaintProcess) + mockComplaintRepo := new(MockComplaint) + usecase := NewComplaintProcessUseCase(mockComplaintProcessRepo, mockComplaintRepo) + dummyComplaintProcess := &entities.ComplaintProcess{ + Message: "Test Message", + Status: "Verifikasi", + ComplaintID: "123", + } + + mockComplaintRepo.On("GetStatus", mock.Anything).Return("On Progress", nil) + + result, err := usecase.Create(dummyComplaintProcess) + + assert.Error(t, err) + assert.Equal(t, entities.ComplaintProcess{}, result) + assert.Equal(t, constants.ErrComplaintAlreadyOnProgress, err) + }) + + t.Run("error when status is On Progress and complaint status is On Progress", func(t *testing.T) { + mockComplaintProcessRepo := new(MockComplaintProcess) + mockComplaintRepo := new(MockComplaint) + usecase := NewComplaintProcessUseCase(mockComplaintProcessRepo, mockComplaintRepo) + dummyComplaintProcess := &entities.ComplaintProcess{ + Message: "Test Message", + Status: "On Progress", + ComplaintID: "123", + } + + mockComplaintRepo.On("GetStatus", mock.Anything).Return("On Progress", nil) + + result, err := usecase.Create(dummyComplaintProcess) + + assert.Error(t, err) + assert.Equal(t, entities.ComplaintProcess{}, result) + assert.Equal(t, constants.ErrComplaintAlreadyOnProgress, err) + }) + + t.Run("error when status is On Progress and complaint status is Ditolak", func(t *testing.T) { + mockComplaintProcessRepo := new(MockComplaintProcess) + mockComplaintRepo := new(MockComplaint) + usecase := NewComplaintProcessUseCase(mockComplaintProcessRepo, mockComplaintRepo) + dummyComplaintProcess := &entities.ComplaintProcess{ + Message: "Test Message", + Status: "On Progress", + ComplaintID: "123", + } + + mockComplaintRepo.On("GetStatus", mock.Anything).Return("Ditolak", nil) + + result, err := usecase.Create(dummyComplaintProcess) + + assert.Error(t, err) + assert.Equal(t, entities.ComplaintProcess{}, result) + assert.Equal(t, constants.ErrComplaintAlreadyRejected, err) + + }) + + t.Run("error when status is On Progress and complaint status is Selesai", func(t *testing.T) { + mockComplaintProcessRepo := new(MockComplaintProcess) + mockComplaintRepo := new(MockComplaint) + usecase := NewComplaintProcessUseCase(mockComplaintProcessRepo, mockComplaintRepo) + dummyComplaintProcess := &entities.ComplaintProcess{ + Message: "Test Message", + Status: "On Progress", + ComplaintID: "123", + } + + mockComplaintRepo.On("GetStatus", mock.Anything).Return("Selesai", nil) + + result, err := usecase.Create(dummyComplaintProcess) + + assert.Error(t, err) + assert.Equal(t, entities.ComplaintProcess{}, result) + assert.Equal(t, constants.ErrComplaintAlreadyFinished, err) + }) + + t.Run("error when status is On Progress and complaint status is Pending", func(t *testing.T) { + mockComplaintProcessRepo := new(MockComplaintProcess) + mockComplaintRepo := new(MockComplaint) + usecase := NewComplaintProcessUseCase(mockComplaintProcessRepo, mockComplaintRepo) + dummyComplaintProcess := &entities.ComplaintProcess{ + Message: "Test Message", + Status: "On Progress", + ComplaintID: "123", + } + + mockComplaintRepo.On("GetStatus", mock.Anything).Return("Pending", nil) + + result, err := usecase.Create(dummyComplaintProcess) + + assert.Error(t, err) + assert.Equal(t, entities.ComplaintProcess{}, result) + assert.Equal(t, constants.ErrComplaintNotVerified, err) + }) + + t.Run("error when status is Selesai and complaint status is Selesai", func(t *testing.T) { + mockComplaintProcessRepo := new(MockComplaintProcess) + mockComplaintRepo := new(MockComplaint) + usecase := NewComplaintProcessUseCase(mockComplaintProcessRepo, mockComplaintRepo) + dummyComplaintProcess := &entities.ComplaintProcess{ + Message: "Test Message", + Status: "Selesai", + ComplaintID: "123", + } + + mockComplaintRepo.On("GetStatus", mock.Anything).Return("Selesai", nil) + + result, err := usecase.Create(dummyComplaintProcess) + + assert.Error(t, err) + assert.Equal(t, entities.ComplaintProcess{}, result) + assert.Equal(t, constants.ErrComplaintAlreadyFinished, err) + }) + + t.Run("error when status is Selesai and complaint status is Ditolak", func(t *testing.T) { + mockComplaintProcessRepo := new(MockComplaintProcess) + mockComplaintRepo := new(MockComplaint) + usecase := NewComplaintProcessUseCase(mockComplaintProcessRepo, mockComplaintRepo) + dummyComplaintProcess := &entities.ComplaintProcess{ + Message: "Test Message", + Status: "Selesai", + ComplaintID: "123", + } + + mockComplaintRepo.On("GetStatus", mock.Anything).Return("Ditolak", nil) + + result, err := usecase.Create(dummyComplaintProcess) + + assert.Error(t, err) + assert.Equal(t, entities.ComplaintProcess{}, result) + assert.Equal(t, constants.ErrComplaintAlreadyRejected, err) + }) + + t.Run("error when status is Selesai and complaint status is Pending", func(t *testing.T) { + mockComplaintProcessRepo := new(MockComplaintProcess) + mockComplaintRepo := new(MockComplaint) + usecase := NewComplaintProcessUseCase(mockComplaintProcessRepo, mockComplaintRepo) + dummyComplaintProcess := &entities.ComplaintProcess{ + Message: "Test Message", + Status: "Selesai", + ComplaintID: "123", + } + + mockComplaintRepo.On("GetStatus", mock.Anything).Return("Pending", nil) + + result, err := usecase.Create(dummyComplaintProcess) + + assert.Error(t, err) + assert.Equal(t, entities.ComplaintProcess{}, result) + assert.Equal(t, constants.ErrComplaintNotVerified, err) + }) + + t.Run("error when status is Selesai and complaint status is Verifikasi", func(t *testing.T) { + mockComplaintProcessRepo := new(MockComplaintProcess) + mockComplaintRepo := new(MockComplaint) + usecase := NewComplaintProcessUseCase(mockComplaintProcessRepo, mockComplaintRepo) + dummyComplaintProcess := &entities.ComplaintProcess{ + Message: "Test Message", + Status: "Selesai", + ComplaintID: "123", + } + + mockComplaintRepo.On("GetStatus", mock.Anything).Return("Verifikasi", nil) + + result, err := usecase.Create(dummyComplaintProcess) + + assert.Error(t, err) + assert.Equal(t, entities.ComplaintProcess{}, result) + assert.Equal(t, constants.ErrComplaintNotOnProgress, err) + }) + + t.Run("error when status is Ditolak and complaint status is Ditolak", func(t *testing.T) { + mockComplaintProcessRepo := new(MockComplaintProcess) + mockComplaintRepo := new(MockComplaint) + usecase := NewComplaintProcessUseCase(mockComplaintProcessRepo, mockComplaintRepo) + dummyComplaintProcess := &entities.ComplaintProcess{ + Message: "Test Message", + Status: "Ditolak", + ComplaintID: "123", + } + + mockComplaintRepo.On("GetStatus", mock.Anything).Return("Ditolak", nil) + + result, err := usecase.Create(dummyComplaintProcess) + + assert.Error(t, err) + assert.Equal(t, entities.ComplaintProcess{}, result) + assert.Equal(t, constants.ErrComplaintAlreadyRejected, err) + }) + + t.Run("error when status is Ditolak and complaint status is Selesai", func(t *testing.T) { + mockComplaintProcessRepo := new(MockComplaintProcess) + mockComplaintRepo := new(MockComplaint) + usecase := NewComplaintProcessUseCase(mockComplaintProcessRepo, mockComplaintRepo) + dummyComplaintProcess := &entities.ComplaintProcess{ + Message: "Test Message", + Status: "Ditolak", + ComplaintID: "123", + } + + mockComplaintRepo.On("GetStatus", mock.Anything).Return("Selesai", nil) + + result, err := usecase.Create(dummyComplaintProcess) + + assert.Error(t, err) + assert.Equal(t, entities.ComplaintProcess{}, result) + assert.Equal(t, constants.ErrComplaintAlreadyFinished, err) + }) + + t.Run("error when status is Ditolak and complaint status is On Progress", func(t *testing.T) { + mockComplaintProcessRepo := new(MockComplaintProcess) + mockComplaintRepo := new(MockComplaint) + usecase := NewComplaintProcessUseCase(mockComplaintProcessRepo, mockComplaintRepo) + dummyComplaintProcess := &entities.ComplaintProcess{ + Message: "Test Message", + Status: "Ditolak", + ComplaintID: "123", + } + + mockComplaintRepo.On("GetStatus", mock.Anything).Return("On Progress", nil) + + result, err := usecase.Create(dummyComplaintProcess) + + assert.Error(t, err) + assert.Equal(t, entities.ComplaintProcess{}, result) + assert.Equal(t, constants.ErrComplaintAlreadyOnProgress, err) + }) + + t.Run("error when status is Ditolak and complaint status is Verifikasi", func(t *testing.T) { + mockComplaintProcessRepo := new(MockComplaintProcess) + mockComplaintRepo := new(MockComplaint) + usecase := NewComplaintProcessUseCase(mockComplaintProcessRepo, mockComplaintRepo) + dummyComplaintProcess := &entities.ComplaintProcess{ + Message: "Test Message", + Status: "Ditolak", + ComplaintID: "123", + } + + mockComplaintRepo.On("GetStatus", mock.Anything).Return("Verifikasi", nil) + + result, err := usecase.Create(dummyComplaintProcess) + + assert.Error(t, err) + assert.Equal(t, entities.ComplaintProcess{}, result) + assert.Equal(t, constants.ErrComplaintAlreadyVerified, err) + }) + +} +func TestComplaintProcessUseCase_GetByComplaintID(t *testing.T) { + t.Run("success", func(t *testing.T) { + mockComplaintProcessRepo := new(MockComplaintProcess) + mockComplaintRepo := new(MockComplaint) + usecase := NewComplaintProcessUseCase(mockComplaintProcessRepo, mockComplaintRepo) + + mockComplaintProcessRepo.On("GetByComplaintID", mock.Anything).Return([]entities.ComplaintProcess{}, nil) + + result, err := usecase.GetByComplaintID("123") + + assert.NoError(t, err) + assert.NotNil(t, result) + }) + + t.Run("internal server error", func(t *testing.T) { + mockComplaintProcessRepo := new(MockComplaintProcess) + mockComplaintRepo := new(MockComplaint) + usecase := NewComplaintProcessUseCase(mockComplaintProcessRepo, mockComplaintRepo) + + mockComplaintProcessRepo.On("GetByComplaintID", mock.Anything).Return([]entities.ComplaintProcess{}, constants.ErrInternalServerError) + + result, err := usecase.GetByComplaintID("123") + + assert.Error(t, err) + assert.Nil(t, result) + assert.Equal(t, constants.ErrInternalServerError, err) + }) + + t.Run("complaint process not found", func(t *testing.T) { + mockComplaintProcessRepo := new(MockComplaintProcess) + mockComplaintRepo := new(MockComplaint) + usecase := NewComplaintProcessUseCase(mockComplaintProcessRepo, mockComplaintRepo) + + mockComplaintProcessRepo.On("GetByComplaintID", mock.Anything).Return([]entities.ComplaintProcess{}, constants.ErrComplaintProcessNotFound) + + result, err := usecase.GetByComplaintID("123") + + assert.Error(t, err) + assert.Nil(t, result) + assert.Equal(t, constants.ErrComplaintProcessNotFound, err) + }) + +} + +func TestComplaintProcessUseCase_Update(t *testing.T) { + t.Run("success", func(t *testing.T) { + mockComplaintProcessRepo := new(MockComplaintProcess) + mockComplaintRepo := new(MockComplaint) + usecase := NewComplaintProcessUseCase(mockComplaintProcessRepo, mockComplaintRepo) + + mockComplaintProcessRepo.On("Update", mock.Anything).Return(nil) + + result, err := usecase.Update(&entities.ComplaintProcess{ + ID: 1, + ComplaintID: "123", + Message: "Test Message", + Status: "Pending", + }) + + assert.NoError(t, err) + assert.NotNil(t, result) + }) + + t.Run("error when message is empty", func(t *testing.T) { + mockComplaintProcessRepo := new(MockComplaintProcess) + mockComplaintRepo := new(MockComplaint) + usecase := NewComplaintProcessUseCase(mockComplaintProcessRepo, mockComplaintRepo) + + result, err := usecase.Update(&entities.ComplaintProcess{ + ID: 1, + ComplaintID: "123", + Message: "", + Status: "Pending", + }) + + assert.Error(t, err) + assert.Equal(t, entities.ComplaintProcess{}, result) + assert.Equal(t, constants.ErrAllFieldsMustBeFilled, err) + }) + + t.Run("error when repository update fails", func(t *testing.T) { + mockComplaintProcessRepo := new(MockComplaintProcess) + mockComplaintRepo := new(MockComplaint) + usecase := NewComplaintProcessUseCase(mockComplaintProcessRepo, mockComplaintRepo) + + mockComplaintProcessRepo.On("Update", mock.Anything).Return(errors.New("update error")) + + result, err := usecase.Update(&entities.ComplaintProcess{ + ID: 1, + ComplaintID: "123", + Message: "Test Message", + Status: "Pending", + }) + + assert.Error(t, err) + assert.Equal(t, entities.ComplaintProcess{}, result) + assert.Equal(t, "update error", err.Error()) + }) + +} + +func TestComplaintProcessUseCase_Delete(t *testing.T) { + t.Run("error when complaintID is empty", func(t *testing.T) { + mockComplaintProcessRepo := new(MockComplaintProcess) + usecase := NewComplaintProcessUseCase(mockComplaintProcessRepo, nil) + + status, err := usecase.Delete("", 1) + + assert.Error(t, err) + assert.Equal(t, "", status) + assert.Equal(t, constants.ErrInvalidIDFormat, err) + }) + + t.Run("error when complaintProcessID is zero", func(t *testing.T) { + mockComplaintProcessRepo := new(MockComplaintProcess) + usecase := NewComplaintProcessUseCase(mockComplaintProcessRepo, nil) + + status, err := usecase.Delete("123", 0) + + assert.Error(t, err) + assert.Equal(t, "", status) + assert.Equal(t, constants.ErrInvalidIDFormat, err) + }) + + t.Run("error when repository delete fails", func(t *testing.T) { + mockComplaintProcessRepo := new(MockComplaintProcess) + usecase := NewComplaintProcessUseCase(mockComplaintProcessRepo, nil) + + mockComplaintProcessRepo.On("Delete", mock.Anything, mock.Anything).Return("", errors.New("delete error")) + + status, err := usecase.Delete("123", 1) + + assert.Error(t, err) + assert.Equal(t, "", status) + assert.Equal(t, "delete error", err.Error()) + }) + + t.Run("success with status Verifikasi", func(t *testing.T) { + mockComplaintProcessRepo := new(MockComplaintProcess) + usecase := NewComplaintProcessUseCase(mockComplaintProcessRepo, nil) + + mockComplaintProcessRepo.On("Delete", mock.Anything, mock.Anything).Return("Verifikasi", nil) + + status, err := usecase.Delete("123", 1) + + assert.NoError(t, err) + assert.Equal(t, "Pending", status) + }) + + t.Run("success with status On Progress", func(t *testing.T) { + mockComplaintProcessRepo := new(MockComplaintProcess) + usecase := NewComplaintProcessUseCase(mockComplaintProcessRepo, nil) + + mockComplaintProcessRepo.On("Delete", mock.Anything, mock.Anything).Return("On Progress", nil) + + status, err := usecase.Delete("123", 1) + + assert.NoError(t, err) + assert.Equal(t, "Verifikasi", status) + }) + + t.Run("success with status Selesai", func(t *testing.T) { + mockComplaintProcessRepo := new(MockComplaintProcess) + usecase := NewComplaintProcessUseCase(mockComplaintProcessRepo, nil) + + mockComplaintProcessRepo.On("Delete", mock.Anything, mock.Anything).Return("Selesai", nil) + + status, err := usecase.Delete("123", 1) + + assert.NoError(t, err) + assert.Equal(t, "On Progress", status) + }) + + t.Run("success with status Ditolak", func(t *testing.T) { + mockComplaintProcessRepo := new(MockComplaintProcess) + usecase := NewComplaintProcessUseCase(mockComplaintProcessRepo, nil) + + mockComplaintProcessRepo.On("Delete", mock.Anything, mock.Anything).Return("Ditolak", nil) + + status, err := usecase.Delete("123", 1) + + assert.NoError(t, err) + assert.Equal(t, "Pending", status) + }) +} diff --git a/usecases/dashboard/dashboard.go b/usecases/dashboard/dashboard.go index 7835604..ca4bac1 100644 --- a/usecases/dashboard/dashboard.go +++ b/usecases/dashboard/dashboard.go @@ -2,15 +2,21 @@ package dashboard import ( "e-complaint-api/controllers/dashboard/response" - "e-complaint-api/drivers/mysql/dashboard" "e-complaint-api/entities" ) +type DashboardRepoInterface interface { + GetTotalComplaints() (int64, error) + GetComplaintsByStatus() (map[string]int64, error) + GetUsersByYearAndMonth() (map[string][]response.MonthData, error) + GetLatestComplaints(limit int) ([]entities.Complaint, error) +} + type DashboardUsecase struct { - DashboardRepo dashboard.DashboardRepo + DashboardRepo DashboardRepoInterface } -func NewDashboardUseCase(dashboardRepo dashboard.DashboardRepo) *DashboardUsecase { +func NewDashboardUseCase(dashboardRepo DashboardRepoInterface) *DashboardUsecase { return &DashboardUsecase{DashboardRepo: dashboardRepo} } diff --git a/usecases/dashboard/dashboard_test.go b/usecases/dashboard/dashboard_test.go new file mode 100644 index 0000000..888b897 --- /dev/null +++ b/usecases/dashboard/dashboard_test.go @@ -0,0 +1,79 @@ +package dashboard_test + +import ( + "e-complaint-api/controllers/dashboard/response" + "e-complaint-api/entities" + "e-complaint-api/usecases/dashboard" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "strconv" + "testing" +) + +type MockDashboardRepo struct { + mock.Mock +} + +func (m *MockDashboardRepo) GetTotalComplaints() (int64, error) { + args := m.Called() + return args.Get(0).(int64), args.Error(1) +} + +func (m *MockDashboardRepo) GetComplaintsByStatus() (map[string]int64, error) { + args := m.Called() + return args.Get(0).(map[string]int64), args.Error(1) +} + +func (m *MockDashboardRepo) GetUsersByYearAndMonth() (map[string][]response.MonthData, error) { + args := m.Called() + return args.Get(0).(map[string][]response.MonthData), args.Error(1) +} + +func (m *MockDashboardRepo) GetLatestComplaints(limit int) ([]entities.Complaint, error) { + args := m.Called(limit) + return args.Get(0).([]entities.Complaint), args.Error(1) +} + +func TestDashboardUsecase_GetTotalComplaints(t *testing.T) { + mockRepo := new(MockDashboardRepo) + mockRepo.On("GetTotalComplaints").Return(int64(10), nil) + + uc := dashboard.NewDashboardUseCase(mockRepo) + total, err := uc.GetTotalComplaints() + + assert.NoError(t, err) + assert.Equal(t, int64(10), total) +} + +func TestDashboardUsecase_GetComplaintsByStatus(t *testing.T) { + mockRepo := new(MockDashboardRepo) + mockRepo.On("GetComplaintsByStatus").Return(map[string]int64{"open": 5, "closed": 5}, nil) + + uc := dashboard.NewDashboardUseCase(mockRepo) + statuses, err := uc.GetComplaintsByStatus() + + assert.NoError(t, err) + assert.Equal(t, map[string]int64{"open": 5, "closed": 5}, statuses) +} + +func TestDashboardUsecase_GetUsersByYearAndMonth(t *testing.T) { + mockRepo := new(MockDashboardRepo) + mockRepo.On("GetUsersByYearAndMonth").Return(map[string][]response.MonthData{"2022": {{Month: "January", Count: 10}}}, nil) + + uc := dashboard.NewDashboardUseCase(mockRepo) + users, err := uc.GetUsersByYearAndMonth() + + assert.NoError(t, err) + assert.Equal(t, map[string][]response.MonthData{"2022": {{Month: "January", Count: 10}}}, users) +} + +func TestDashboardUsecase_GetLatestComplaints(t *testing.T) { + mockRepo := new(MockDashboardRepo) + mockRepo.On("GetLatestComplaints", 5).Return([]entities.Complaint{{ID: strconv.Itoa(1)}, {ID: strconv.Itoa(2)}, {ID: strconv.Itoa(3)}, {ID: strconv.Itoa(4)}, {ID: strconv.Itoa(5)}}, nil) + + uc := dashboard.NewDashboardUseCase(mockRepo) + complaints, err := uc.GetLatestComplaints(5) + + assert.NoError(t, err) + assert.Equal(t, []entities.Complaint{{ID: "1"}, {ID: "2"}, {ID: "3"}, {ID: "4"}, {ID: "5"}}, complaints) +} diff --git a/usecases/discussion/discussion_test.go b/usecases/discussion/discussion_test.go new file mode 100644 index 0000000..27685f6 --- /dev/null +++ b/usecases/discussion/discussion_test.go @@ -0,0 +1,380 @@ +package discussion + +import ( + "e-complaint-api/constants" + "e-complaint-api/entities" + "errors" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "testing" +) + +type MockDiscussion struct { + mock.Mock +} + +func (m *MockDiscussion) Create(discussion *entities.Discussion) error { + args := m.Called(discussion) + return args.Error(0) +} + +func (m *MockDiscussion) GetById(id int) (*entities.Discussion, error) { + args := m.Called(id) + return args.Get(0).(*entities.Discussion), args.Error(1) +} + +func (m *MockDiscussion) GetByComplaintID(complaintID string) (*[]entities.Discussion, error) { + args := m.Called(complaintID) + return args.Get(0).(*[]entities.Discussion), args.Error(1) +} + +func (m *MockDiscussion) Update(discussion *entities.Discussion) error { + args := m.Called(discussion) + return args.Error(0) +} + +func (m *MockDiscussion) Delete(id int) error { + args := m.Called(id) + return args.Error(0) +} + +type MockFaq struct { + mock.Mock +} + +func (m *MockFaq) GetAll() ([]entities.Faq, error) { + args := m.Called() + return args.Get(0).([]entities.Faq), args.Error(1) + +} + +type OpenAIAPI struct { + mock.Mock +} + +func (m *OpenAIAPI) GetResponse(text string) (string, error) { + args := m.Called(text) + return args.String(0), args.Error(1) +} + +func (m *OpenAIAPI) GetChatCompletion(prompt []string, userPrompt string) (string, error) { + args := m.Called(prompt, userPrompt) + return args.String(0), args.Error(1) +} + +func TestDiscussionUseCase_GetById(t *testing.T) { + t.Run("success", func(t *testing.T) { + mockDiscussion := new(MockDiscussion) + mockFaq := new(MockFaq) + mockOpenAIAPI := new(OpenAIAPI) + useCase := NewDiscussionUseCase(mockDiscussion, mockFaq, mockOpenAIAPI) + + discussion := entities.Discussion{ + ID: 1, + Comment: "Hello", + } + + mockDiscussion.On("GetById", 1).Return(&discussion, nil) + result, err := useCase.GetById(1) + assert.Nil(t, err) + assert.NotNil(t, result) + + }) + + t.Run("discussion not found", func(t *testing.T) { + mockDiscussion := new(MockDiscussion) + mockFaq := new(MockFaq) + mockOpenAIAPI := new(OpenAIAPI) + useCase := NewDiscussionUseCase(mockDiscussion, mockFaq, mockOpenAIAPI) + + mockDiscussion.On("GetById", 1).Return((*entities.Discussion)(nil), constants.ErrDiscussionNotFound) + result, err := useCase.GetById(1) + assert.NotNil(t, err) + assert.Nil(t, result) + }) + + t.Run("error", func(t *testing.T) { + mockDiscussion := new(MockDiscussion) + mockFaq := new(MockFaq) + mockOpenAIAPI := new(OpenAIAPI) + useCase := NewDiscussionUseCase(mockDiscussion, mockFaq, mockOpenAIAPI) + + mockDiscussion.On("GetById", 1).Return((*entities.Discussion)(nil), constants.ErrInternalServerError) + result, err := useCase.GetById(1) + assert.NotNil(t, err) + assert.Nil(t, result) + }) + +} + +func TestDiscussionUseCase_Create(t *testing.T) { + t.Run("success", func(t *testing.T) { + mockDiscussion := new(MockDiscussion) + mockFaq := new(MockFaq) + mockOpenAIAPI := new(OpenAIAPI) + useCase := NewDiscussionUseCase(mockDiscussion, mockFaq, mockOpenAIAPI) + + discussion := entities.Discussion{ + Comment: "Hello", + } + + mockDiscussion.On("Create", &discussion).Return(nil) + err := useCase.Create(&discussion) + assert.Nil(t, err) + }) + + t.Run("comment cannot be empty", func(t *testing.T) { + mockDiscussion := new(MockDiscussion) + mockFaq := new(MockFaq) + mockOpenAIAPI := new(OpenAIAPI) + useCase := NewDiscussionUseCase(mockDiscussion, mockFaq, mockOpenAIAPI) + + discussion := entities.Discussion{ + Comment: "", + } + + err := useCase.Create(&discussion) + assert.NotNil(t, err) + assert.Equal(t, constants.ErrCommentCannotBeEmpty, err) + }) + + t.Run("error", func(t *testing.T) { + mockDiscussion := new(MockDiscussion) + mockFaq := new(MockFaq) + mockOpenAIAPI := new(OpenAIAPI) + useCase := NewDiscussionUseCase(mockDiscussion, mockFaq, mockOpenAIAPI) + + discussion := entities.Discussion{ + Comment: "Hello", + } + + mockDiscussion.On("Create", &discussion).Return(constants.ErrInternalServerError) + err := useCase.Create(&discussion) + assert.NotNil(t, err) + assert.Equal(t, constants.ErrInternalServerError, err) + }) +} + +func TestDiscussionUseCase_Update(t *testing.T) { + t.Run("success", func(t *testing.T) { + mockDiscussion := new(MockDiscussion) + mockFaq := new(MockFaq) + mockOpenAIAPI := new(OpenAIAPI) + useCase := NewDiscussionUseCase(mockDiscussion, mockFaq, mockOpenAIAPI) + + discussion := entities.Discussion{ + Comment: "Hello", + } + + mockDiscussion.On("Update", &discussion).Return(nil) + err := useCase.Update(&discussion) + assert.Nil(t, err) + }) + + t.Run("comment cannot be empty", func(t *testing.T) { + mockDiscussion := new(MockDiscussion) + mockFaq := new(MockFaq) + mockOpenAIAPI := new(OpenAIAPI) + useCase := NewDiscussionUseCase(mockDiscussion, mockFaq, mockOpenAIAPI) + + discussion := entities.Discussion{ + Comment: "", + } + + err := useCase.Update(&discussion) + assert.NotNil(t, err) + assert.Equal(t, constants.ErrCommentCannotBeEmpty, err) + }) + + t.Run("error", func(t *testing.T) { + mockDiscussion := new(MockDiscussion) + mockFaq := new(MockFaq) + mockOpenAIAPI := new(OpenAIAPI) + useCase := NewDiscussionUseCase(mockDiscussion, mockFaq, mockOpenAIAPI) + + discussion := entities.Discussion{ + Comment: "Hello", + } + + mockDiscussion.On("Update", &discussion).Return(constants.ErrInternalServerError) + err := useCase.Update(&discussion) + assert.NotNil(t, err) + assert.Equal(t, constants.ErrInternalServerError, err) + }) +} + +func TestDiscussionUseCase_Delete(t *testing.T) { + t.Run("success", func(t *testing.T) { + mockDiscussion := new(MockDiscussion) + mockFaq := new(MockFaq) + mockOpenAIAPI := new(OpenAIAPI) + useCase := NewDiscussionUseCase(mockDiscussion, mockFaq, mockOpenAIAPI) + + mockDiscussion.On("Delete", 1).Return(nil) + err := useCase.Delete(1) + assert.Nil(t, err) + }) + + t.Run("error", func(t *testing.T) { + mockDiscussion := new(MockDiscussion) + mockFaq := new(MockFaq) + mockOpenAIAPI := new(OpenAIAPI) + useCase := NewDiscussionUseCase(mockDiscussion, mockFaq, mockOpenAIAPI) + + mockDiscussion.On("Delete", 1).Return(constants.ErrInternalServerError) + err := useCase.Delete(1) + assert.NotNil(t, err) + assert.Equal(t, constants.ErrInternalServerError, err) + }) + +} + +func TestDiscussionUseCase_GetByComplaintID(t *testing.T) { + t.Run("success", func(t *testing.T) { + mockDiscussion := new(MockDiscussion) + mockFaq := new(MockFaq) + mockOpenAIAPI := new(OpenAIAPI) + useCase := NewDiscussionUseCase(mockDiscussion, mockFaq, mockOpenAIAPI) + + discussions := []entities.Discussion{ + { + ID: 1, + ComplaintID: "1", + Comment: "Hello", + }, + } + + mockDiscussion.On("GetByComplaintID", "1").Return(&discussions, nil) + result, err := useCase.GetByComplaintID("1") + assert.Nil(t, err) + assert.NotNil(t, result) + }) + + t.Run("discussion not found", func(t *testing.T) { + mockDiscussion := new(MockDiscussion) + mockFaq := new(MockFaq) + mockOpenAIAPI := new(OpenAIAPI) + useCase := NewDiscussionUseCase(mockDiscussion, mockFaq, mockOpenAIAPI) + + mockDiscussion.On("GetByComplaintID", "1").Return((*[]entities.Discussion)(nil), constants.ErrDiscussionNotFound) + result, err := useCase.GetByComplaintID("1") + assert.NotNil(t, err) + assert.Nil(t, result) + }) +} + +func TestDiscussionUseCase_GetAnswerRecommendation(t *testing.T) { + t.Run("success", func(t *testing.T) { + mockDiscussionRepo := new(MockDiscussion) + mockFaqRepo := new(MockFaq) + mockOpenAIAPI := new(OpenAIAPI) + useCase := NewDiscussionUseCase(mockDiscussionRepo, mockFaqRepo, mockOpenAIAPI) + + discussions := []entities.Discussion{ + { + ID: 1, + ComplaintID: "1", + Comment: "Hello", + }, + } + + faqs := []entities.Faq{ + { + Question: "What is the meaning of life?", + Answer: "42", + }, + } + + mockDiscussionRepo.On("GetByComplaintID", "1").Return(&discussions, nil) + mockFaqRepo.On("GetAll").Return(faqs, nil) + mockOpenAIAPI.On("GetChatCompletion", mock.Anything, "").Return("Test response", nil) + + result, err := useCase.GetAnswerRecommendation("1") + assert.Nil(t, err) + assert.Equal(t, "Test response", result) + }) + + t.Run("error", func(t *testing.T) { + mockDiscussionRepo := new(MockDiscussion) + mockFaqRepo := new(MockFaq) + mockOpenAIAPI := new(OpenAIAPI) + useCase := NewDiscussionUseCase(mockDiscussionRepo, mockFaqRepo, mockOpenAIAPI) + + mockDiscussionRepo.On("GetByComplaintID", "1").Return((*[]entities.Discussion)(nil), constants.ErrDiscussionNotFound) + + result, err := useCase.GetAnswerRecommendation("1") + assert.NotNil(t, err) + assert.Equal(t, "", result) + }) + + t.Run("GetAll returns error", func(t *testing.T) { + mockDiscussionRepo := new(MockDiscussion) + mockFaqRepo := new(MockFaq) + mockOpenAIAPI := new(OpenAIAPI) + useCase := NewDiscussionUseCase(mockDiscussionRepo, mockFaqRepo, mockOpenAIAPI) + mockDiscussionRepo.On("GetByComplaintID", "1").Return((*[]entities.Discussion)(nil), nil) + mockFaqRepo.On("GetAll").Return([]entities.Faq(nil), errors.New("some error")) + _, err := useCase.GetAnswerRecommendation("1") + assert.Error(t, err) + }) + + t.Run("GetChatCompletion returns error", func(t *testing.T) { + mockDiscussionRepo := new(MockDiscussion) + mockFaqRepo := new(MockFaq) + mockOpenAIAPI := new(OpenAIAPI) + useCase := NewDiscussionUseCase(mockDiscussionRepo, mockFaqRepo, mockOpenAIAPI) + mockDiscussionRepo.On("GetByComplaintID", "1").Return((*[]entities.Discussion)(nil), nil) + mockFaqRepo.On("GetAll").Return([]entities.Faq{{Question: "What is the meaning of life?", Answer: "42"}}, nil) + mockOpenAIAPI.On("GetChatCompletion", mock.Anything, "").Return("", errors.New("some error")) + _, err := useCase.GetAnswerRecommendation("1") + assert.Error(t, err) + }) + + t.Run("UserID is not nil", func(t *testing.T) { + mockDiscussionRepo := new(MockDiscussion) + mockFaqRepo := new(MockFaq) + mockOpenAIAPI := new(OpenAIAPI) + useCase := NewDiscussionUseCase(mockDiscussionRepo, mockFaqRepo, mockOpenAIAPI) + + userID := 1 + discussions := []entities.Discussion{ + { + ID: 1, + UserID: &userID, + Comment: "Hello", + }, + } + + mockDiscussionRepo.On("GetByComplaintID", "1").Return(&discussions, nil) + mockFaqRepo.On("GetAll").Return([]entities.Faq{}, nil) + mockOpenAIAPI.On("GetChatCompletion", mock.Anything, "").Return("1.)User: Hello\nTest response", nil) + + result, err := useCase.GetAnswerRecommendation("1") + assert.Nil(t, err) + assert.Contains(t, result, "1.)User: Hello") + }) + + t.Run("UserID is nil", func(t *testing.T) { + mockDiscussionRepo := new(MockDiscussion) + mockFaqRepo := new(MockFaq) + mockOpenAIAPI := new(OpenAIAPI) + useCase := NewDiscussionUseCase(mockDiscussionRepo, mockFaqRepo, mockOpenAIAPI) + + discussions := []entities.Discussion{ + { + ID: 1, + UserID: nil, + Comment: "Hello", + }, + } + + mockDiscussionRepo.On("GetByComplaintID", "1").Return(&discussions, nil) + mockFaqRepo.On("GetAll").Return([]entities.Faq{}, nil) + mockOpenAIAPI.On("GetChatCompletion", mock.Anything, "").Return("Test response", nil) + + result, err := useCase.GetAnswerRecommendation("1") + assert.Nil(t, err) + assert.NotContains(t, result, "1.)User: Hello") + }) + +} diff --git a/usecases/news/news_test.go b/usecases/news/news_test.go new file mode 100644 index 0000000..6648fe1 --- /dev/null +++ b/usecases/news/news_test.go @@ -0,0 +1,424 @@ +package news + +import ( + "e-complaint-api/constants" + "e-complaint-api/entities" + "errors" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "testing" +) + +type MockNews struct { + mock.Mock +} + +func (m *MockNews) GetPaginated(limit int, page int, search string, filter map[string]interface{}, sortBy string, sortType string) ([]entities.News, error) { + args := m.Called(limit, page, search, filter, sortBy, sortType) + return args.Get(0).([]entities.News), args.Error(1) +} + +func (m *MockNews) GetMetaData(limit int, page int, search string, filter map[string]interface{}) (entities.Metadata, error) { + args := m.Called(limit, page, search, filter) + return args.Get(0).(entities.Metadata), args.Error(1) +} + +func (m *MockNews) GetByID(id int) (entities.News, error) { + args := m.Called(id) + return args.Get(0).(entities.News), args.Error(1) +} + +func (m *MockNews) Create(news *entities.News) error { + args := m.Called(news) + return args.Error(0) +} + +func (m *MockNews) Update(news entities.News) (entities.News, error) { + args := m.Called(news) + return args.Get(0).(entities.News), args.Error(1) +} + +func (m *MockNews) Delete(id int) error { + args := m.Called(id) + return args.Error(0) +} + +func TestNewsUseCase_GetPaginated(t *testing.T) { + t.Run("success", func(t *testing.T) { + mockNews := new(MockNews) + useCase := NewNewsUseCase(mockNews) + news := []entities.News{ + { + ID: 1, + Title: "title", + Content: "content", + CategoryID: 1, + }, + { + ID: 2, + Title: "title2", + Content: "content2", + }, + { + ID: 3, + Title: "title3", + Content: "content3", + }, + } + mockNews.On("GetPaginated", 10, 1, "", map[string]interface{}{}, "created_at", "DESC").Return(news, nil) + _, err := useCase.GetPaginated(10, 1, "", map[string]interface{}{}, "created_at", "DESC") + assert.NoError(t, err) + }) + + t.Run("success - sort by is empty", func(t *testing.T) { + mockNews := new(MockNews) + useCase := NewNewsUseCase(mockNews) + mockNews.On("GetPaginated", 10, 1, "", map[string]interface{}{}, "created_at", "DESC").Return([]entities.News{}, nil) + _, err := useCase.GetPaginated(10, 1, "", map[string]interface{}{}, "", "DESC") + assert.NoError(t, err) + }) + + t.Run("success - sort type is empty", func(t *testing.T) { + mockNews := new(MockNews) + useCase := NewNewsUseCase(mockNews) + mockNews.On("GetPaginated", 10, 1, "", map[string]interface{}{}, "created_at", "DESC").Return([]entities.News{}, nil) + _, err := useCase.GetPaginated(10, 1, "", map[string]interface{}{}, "created_at", "") + assert.NoError(t, err) + }) + + t.Run("error - page must be filled", func(t *testing.T) { + mockNews := new(MockNews) + useCase := NewNewsUseCase(mockNews) + _, err := useCase.GetPaginated(10, 0, "", map[string]interface{}{}, "created_at", "DESC") + assert.Error(t, err) + assert.Equal(t, constants.ErrPageMustBeFilled, err) + }) + + t.Run("error - limit must be filled", func(t *testing.T) { + mockNews := new(MockNews) + useCase := NewNewsUseCase(mockNews) + _, err := useCase.GetPaginated(0, 1, "", map[string]interface{}{}, "created_at", "DESC") + assert.Error(t, err) + assert.Equal(t, constants.ErrLimitMustBeFilled, err) + }) + + t.Run("error", func(t *testing.T) { + mockNews := new(MockNews) + useCase := NewNewsUseCase(mockNews) + mockNews.On("GetPaginated", 10, 1, "", map[string]interface{}{}, "created_at", "DESC").Return([]entities.News{}, constants.ErrInternalServerError) + _, err := useCase.GetPaginated(10, 1, "", map[string]interface{}{}, "created_at", "DESC") + assert.Error(t, err) + + }) + + t.Run("invalid sort type", func(t *testing.T) { + mockNews := new(MockNews) + useCase := NewNewsUseCase(mockNews) + mockNews.On("GetPaginated", 10, 1, "", map[string]interface{}{}, "created_at", "INVALID").Return([]entities.News{}, errors.New("invalid sort type")) + _, err := useCase.GetPaginated(10, 1, "", map[string]interface{}{}, "created_at", "INVALID") + assert.Error(t, err) + }) + + t.Run("error - page must be filled", func(t *testing.T) { + mockNews := new(MockNews) + useCase := NewNewsUseCase(mockNews) + _, err := useCase.GetPaginated(10, 0, "", map[string]interface{}{}, "created_at", "DESC") + assert.Error(t, err) + assert.Equal(t, constants.ErrPageMustBeFilled, err) + }) + + t.Run("error - limit must be filled", func(t *testing.T) { + mockNews := new(MockNews) + useCase := NewNewsUseCase(mockNews) + _, err := useCase.GetPaginated(0, 1, "", map[string]interface{}{}, "created_at", "DESC") + assert.Error(t, err) + assert.Equal(t, constants.ErrLimitMustBeFilled, err) + }) + +} + +func TestNewsUseCase_Create(t *testing.T) { + t.Run("success", func(t *testing.T) { + mockNews := new(MockNews) + useCase := NewNewsUseCase(mockNews) + news := entities.News{ + Title: "title", + Content: "content", + CategoryID: 1, + } + mockNews.On("Create", &news).Return(nil) + _, err := useCase.Create(&news) + assert.NoError(t, err) + }) + + t.Run("error", func(t *testing.T) { + mockNews := new(MockNews) + useCase := NewNewsUseCase(mockNews) + news := entities.News{ + Title: "title", + Content: "content", + CategoryID: 1, + } + mockNews.On("Create", &news).Return(constants.ErrInternalServerError) + _, err := useCase.Create(&news) + assert.Error(t, err) + }) + + t.Run("empty title", func(t *testing.T) { + mockNews := new(MockNews) + useCase := NewNewsUseCase(mockNews) + news := entities.News{ + Title: "", + Content: "content", + CategoryID: 1, + } + mockNews.On("Create", &news).Return(constants.ErrAllFieldsMustBeFilled) + _, err := useCase.Create(&news) + assert.Error(t, err) + }) + + t.Run("empty content", func(t *testing.T) { + mockNews := new(MockNews) + useCase := NewNewsUseCase(mockNews) + news := entities.News{ + Title: "title", + Content: "", + CategoryID: 1, + } + mockNews.On("Create", &news).Return(constants.ErrAllFieldsMustBeFilled) + _, err := useCase.Create(&news) + assert.Error(t, err) + }) + + t.Run("empty category id", func(t *testing.T) { + mockNews := new(MockNews) + useCase := NewNewsUseCase(mockNews) + news := entities.News{ + Title: "title", + Content: "content", + CategoryID: 0, + } + mockNews.On("Create", &news).Return(constants.ErrAllFieldsMustBeFilled) + _, err := useCase.Create(&news) + assert.Error(t, err) + }) + + t.Run("error - category not found due to reference error", func(t *testing.T) { + mockNews := new(MockNews) + useCase := NewNewsUseCase(mockNews) + news := entities.News{ + Title: "title", + Content: "content", + CategoryID: 999, + } + mockNews.On("Create", &news).Return(errors.New("foreign key constraint fails (`e-complaint-api`.`news`, CONSTRAINT `news_ibfk_1` FOREIGN KEY (`category_id`) REFERENCES `categories` (`id`))")) + _, err := useCase.Create(&news) + assert.Error(t, err) + assert.Equal(t, constants.ErrCategoryNotFound, err) + }) +} + +func TestNewsUseCase_GetByID(t *testing.T) { + t.Run("success", func(t *testing.T) { + mockNews := new(MockNews) + useCase := NewNewsUseCase(mockNews) + news := entities.News{ + ID: 1, + Title: "title", + Content: "content", + CategoryID: 1, + } + mockNews.On("GetByID", 1).Return(news, nil) + _, err := useCase.GetByID(1) + assert.NoError(t, err) + }) + + t.Run("error", func(t *testing.T) { + mockNews := new(MockNews) + useCase := NewNewsUseCase(mockNews) + mockNews.On("GetByID", 1).Return(entities.News{}, constants.ErrInternalServerError) + _, err := useCase.GetByID(1) + assert.Error(t, err) + }) +} + +func TestNewsUseCase_Delete(t *testing.T) { + t.Run("success", func(t *testing.T) { + mockNews := new(MockNews) + useCase := NewNewsUseCase(mockNews) + mockNews.On("Delete", 1).Return(nil) + err := useCase.Delete(1) + assert.NoError(t, err) + }) + + t.Run("error", func(t *testing.T) { + mockNews := new(MockNews) + useCase := NewNewsUseCase(mockNews) + mockNews.On("Delete", 1).Return(constants.ErrInternalServerError) + err := useCase.Delete(1) + assert.Error(t, err) + }) +} + +func TestNewsUseCase_Update(t *testing.T) { + t.Run("success", func(t *testing.T) { + mockNews := new(MockNews) + useCase := NewNewsUseCase(mockNews) + news := entities.News{ + ID: 1, + Title: "title", + Content: "content", + CategoryID: 1, + } + mockNews.On("Update", news).Return(news, nil) + _, err := useCase.Update(news) + assert.NoError(t, err) + }) + + t.Run("error", func(t *testing.T) { + mockNews := new(MockNews) + useCase := NewNewsUseCase(mockNews) + news := entities.News{ + ID: 1, + Title: "title", + Content: "content", + CategoryID: 1, + } + mockNews.On("Update", news).Return(entities.News{}, constants.ErrInternalServerError) + _, err := useCase.Update(news) + assert.Error(t, err) + }) + + t.Run("empty title", func(t *testing.T) { + mockNews := new(MockNews) + useCase := NewNewsUseCase(mockNews) + news := entities.News{ + ID: 1, + Title: "", + Content: "content", + CategoryID: 1, + } + mockNews.On("Update", news).Return(entities.News{}, constants.ErrAllFieldsMustBeFilled) + _, err := useCase.Update(news) + assert.Error(t, err) + }) + + t.Run("empty content", func(t *testing.T) { + mockNews := new(MockNews) + useCase := NewNewsUseCase(mockNews) + news := entities.News{ + ID: 1, + Title: "title", + Content: "", + CategoryID: 1, + } + mockNews.On("Update", news).Return(entities.News{}, constants.ErrAllFieldsMustBeFilled) + _, err := useCase.Update(news) + assert.Error(t, err) + }) + + t.Run("empty category id", func(t *testing.T) { + mockNews := new(MockNews) + useCase := NewNewsUseCase(mockNews) + news := entities.News{ + ID: 1, + Title: "title", + Content: "content", + CategoryID: 0, + } + mockNews.On("Update", news).Return(entities.News{}, constants.ErrAllFieldsMustBeFilled) + _, err := useCase.Update(news) + assert.Error(t, err) + }) + + t.Run("error - category not found due to reference error", func(t *testing.T) { + mockNews := new(MockNews) + useCase := NewNewsUseCase(mockNews) + news := entities.News{ + ID: 1, + Title: "title", + Content: "content", + CategoryID: 999, + } + mockNews.On("Update", news).Return(entities.News{}, errors.New("foreign key constraint fails (`e-complaint-api`.`news`, CONSTRAINT `news_ibfk_1` FOREIGN KEY (`category_id`) REFERENCES `categories` (`id`))")) + _, err := useCase.Update(news) + assert.Error(t, err) + assert.Equal(t, constants.ErrCategoryNotFound, err) + }) +} + +func TestNewsUseCase_GetMetaData(t *testing.T) { + t.Run("success - with limit and page", func(t *testing.T) { + mockNews := new(MockNews) + useCase := NewNewsUseCase(mockNews) + mockNews.On("GetMetaData", 10, 1, "", map[string]interface{}{}).Return(entities.Metadata{TotalData: 100}, nil) + metaData, err := useCase.GetMetaData(10, 1, "", map[string]interface{}{}) + assert.NoError(t, err) + assert.Equal(t, 1, metaData.Pagination.FirstPage) + assert.Equal(t, 10, metaData.Pagination.LastPage) + assert.Equal(t, 1, metaData.Pagination.CurrentPage) + assert.Equal(t, 10, metaData.Pagination.TotalDataPerPage) + assert.Equal(t, 0, metaData.Pagination.PrevPage) + assert.Equal(t, 2, metaData.Pagination.NextPage) + }) + + t.Run("success - without limit and page", func(t *testing.T) { + mockNews := new(MockNews) + useCase := NewNewsUseCase(mockNews) + mockNews.On("GetMetaData", 0, 0, "", map[string]interface{}{}).Return(entities.Metadata{TotalData: 100}, nil) + metaData, err := useCase.GetMetaData(0, 0, "", map[string]interface{}{}) + assert.NoError(t, err) + assert.Equal(t, 1, metaData.Pagination.FirstPage) + assert.Equal(t, 1, metaData.Pagination.LastPage) + assert.Equal(t, 1, metaData.Pagination.CurrentPage) + assert.Equal(t, 100, metaData.Pagination.TotalDataPerPage) + assert.Equal(t, 0, metaData.Pagination.PrevPage) + assert.Equal(t, 0, metaData.Pagination.NextPage) + }) + + t.Run("success - current page is last page", func(t *testing.T) { + mockNews := new(MockNews) + useCase := NewNewsUseCase(mockNews) + mockNews.On("GetMetaData", 10, 10, "", map[string]interface{}{}).Return(entities.Metadata{TotalData: 100}, nil) + metaData, err := useCase.GetMetaData(10, 10, "", map[string]interface{}{}) + assert.NoError(t, err) + assert.Equal(t, 10, metaData.Pagination.TotalDataPerPage) + }) + + t.Run("success - page is greater than 1", func(t *testing.T) { + mockNews := new(MockNews) + useCase := NewNewsUseCase(mockNews) + mockNews.On("GetMetaData", 10, 2, "", map[string]interface{}{}).Return(entities.Metadata{TotalData: 100}, nil) + metaData, err := useCase.GetMetaData(10, 2, "", map[string]interface{}{}) + assert.NoError(t, err) + assert.Equal(t, 1, metaData.Pagination.PrevPage) + }) + + t.Run("success - page is less than last page", func(t *testing.T) { + mockNews := new(MockNews) + useCase := NewNewsUseCase(mockNews) + mockNews.On("GetMetaData", 10, 1, "", map[string]interface{}{}).Return(entities.Metadata{TotalData: 100}, nil) + metaData, err := useCase.GetMetaData(10, 1, "", map[string]interface{}{}) + assert.NoError(t, err) + assert.Equal(t, 2, metaData.Pagination.NextPage) + }) + + t.Run("success - page is equal to last page", func(t *testing.T) { + mockNews := new(MockNews) + useCase := NewNewsUseCase(mockNews) + mockNews.On("GetMetaData", 10, 10, "", map[string]interface{}{}).Return(entities.Metadata{TotalData: 100}, nil) + metaData, err := useCase.GetMetaData(10, 10, "", map[string]interface{}{}) + assert.NoError(t, err) + assert.Equal(t, 0, metaData.Pagination.NextPage) + }) + + t.Run("error - internal server error", func(t *testing.T) { + mockNews := new(MockNews) + useCase := NewNewsUseCase(mockNews) + mockNews.On("GetMetaData", 10, 1, "", map[string]interface{}{}).Return(entities.Metadata{}, errors.New("internal server error")) + _, err := useCase.GetMetaData(10, 1, "", map[string]interface{}{}) + assert.Error(t, err) + assert.Equal(t, constants.ErrInternalServerError, err) + }) + +} diff --git a/usecases/news_comment/news_comment_test.go b/usecases/news_comment/news_comment_test.go new file mode 100644 index 0000000..af7fd3d --- /dev/null +++ b/usecases/news_comment/news_comment_test.go @@ -0,0 +1,152 @@ +package news_comment + +import ( + "e-complaint-api/constants" + "e-complaint-api/entities" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "testing" +) + +type MockComment struct { + mock.Mock +} + +func (nc *MockComment) CommentNews(newsComment *entities.NewsComment) error { + args := nc.Called(newsComment) + return args.Error(0) +} + +func (nc *MockComment) GetById(id int) (*entities.NewsComment, error) { + args := nc.Called(id) + return args.Get(0).(*entities.NewsComment), args.Error(1) +} + +func (nc *MockComment) GetByNewsId(newsId int) ([]entities.NewsComment, error) { + args := nc.Called(newsId) + return args.Get(0).([]entities.NewsComment), args.Error(1) +} + +func (nc *MockComment) UpdateComment(newsComment *entities.NewsComment) error { + args := nc.Called(newsComment) + return args.Error(0) +} + +func (nc *MockComment) DeleteComment(id int) error { + args := nc.Called(id) + return args.Error(0) +} + +func TestNewsCommentUseCase_CommentNews(t *testing.T) { + t.Run("success", func(t *testing.T) { + mockRepo := new(MockComment) + ncu := NewNewsCommentUseCase(mockRepo) + comment := &entities.NewsComment{ + Comment: "Test comment", + } + mockRepo.On("CommentNews", comment).Return(nil) + err := ncu.CommentNews(comment) + assert.Nil(t, err) + }) + + t.Run("comment cannot be empty", func(t *testing.T) { + mockRepo := new(MockComment) + ncu := NewNewsCommentUseCase(mockRepo) + emptyComment := &entities.NewsComment{ + Comment: "", + } + mockRepo.On("CommentNews", emptyComment).Return(nil) + err := ncu.CommentNews(emptyComment) + assert.Equal(t, constants.ErrCommentCannotBeEmpty, err) + }) + + t.Run("error", func(t *testing.T) { + mockRepo := new(MockComment) + ncu := NewNewsCommentUseCase(mockRepo) + comment := &entities.NewsComment{ + Comment: "Test comment", + } + mockRepo.On("CommentNews", comment).Return(constants.ErrInternalServerError) + err := ncu.CommentNews(comment) + assert.Equal(t, constants.ErrInternalServerError, err) + }) +} + +func TestNewsCommentUseCase_GetById(t *testing.T) { + t.Run("success", func(t *testing.T) { + mockRepo := new(MockComment) + ncu := NewNewsCommentUseCase(mockRepo) + mockRepo.On("GetById", 1).Return(&entities.NewsComment{}, nil) + _, err := ncu.GetById(1) + assert.Nil(t, err) + }) + + t.Run("error", func(t *testing.T) { + mockRepo := new(MockComment) + ncu := NewNewsCommentUseCase(mockRepo) + mockRepo.On("GetById", 1).Return(&entities.NewsComment{}, constants.ErrInternalServerError) + _, err := ncu.GetById(1) + assert.Equal(t, constants.ErrInternalServerError, err) + }) + +} + +func TestNewsCommentUseCase_GetByNewsId(t *testing.T) { + t.Run("success", func(t *testing.T) { + mockRepo := new(MockComment) + ncu := NewNewsCommentUseCase(mockRepo) + mockRepo.On("GetByNewsId", 1).Return([]entities.NewsComment{}, nil) + _, err := ncu.GetByNewsId(1) + assert.Nil(t, err) + }) + + t.Run("error", func(t *testing.T) { + mockRepo := new(MockComment) + ncu := NewNewsCommentUseCase(mockRepo) + mockRepo.On("GetByNewsId", 1).Return([]entities.NewsComment{}, constants.ErrInternalServerError) + _, err := ncu.GetByNewsId(1) + assert.Equal(t, constants.ErrInternalServerError, err) + }) +} + +func TestNewsCommentUseCase_UpdateComment(t *testing.T) { + t.Run("success", func(t *testing.T) { + mockRepo := new(MockComment) + ncu := NewNewsCommentUseCase(mockRepo) + comment := &entities.NewsComment{ + Comment: "Test comment", + } + mockRepo.On("UpdateComment", comment).Return(nil) + err := ncu.UpdateComment(comment) + assert.Nil(t, err) + }) + + t.Run("error", func(t *testing.T) { + mockRepo := new(MockComment) + ncu := NewNewsCommentUseCase(mockRepo) + comment := &entities.NewsComment{ + Comment: "Test comment", + } + mockRepo.On("UpdateComment", comment).Return(constants.ErrInternalServerError) + err := ncu.UpdateComment(comment) + assert.Equal(t, constants.ErrInternalServerError, err) + }) +} + +func TestNewsCommentUseCase_DeleteComment(t *testing.T) { + t.Run("success", func(t *testing.T) { + mockRepo := new(MockComment) + ncu := NewNewsCommentUseCase(mockRepo) + mockRepo.On("DeleteComment", 1).Return(nil) + err := ncu.DeleteComment(1) + assert.Nil(t, err) + }) + + t.Run("error", func(t *testing.T) { + mockRepo := new(MockComment) + ncu := NewNewsCommentUseCase(mockRepo) + mockRepo.On("DeleteComment", 1).Return(constants.ErrInternalServerError) + err := ncu.DeleteComment(1) + assert.Equal(t, constants.ErrInternalServerError, err) + }) +} diff --git a/usecases/news_file/news_file_test.go b/usecases/news_file/news_file_test.go new file mode 100644 index 0000000..8970a3f --- /dev/null +++ b/usecases/news_file/news_file_test.go @@ -0,0 +1,121 @@ +package news_file + +import ( + "e-complaint-api/entities" + "errors" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "mime/multipart" + "testing" +) + +type NewsFileMock struct { + mock.Mock +} + +func (m *NewsFileMock) Create(newsFiles []*entities.NewsFile) error { + args := m.Called(newsFiles) + return args.Error(0) +} +func (m *NewsFileMock) DeleteByNewsID(newsID int) error { + args := m.Called(newsID) + return args.Error(0) +} + +type NewsFileGCSAPIMock struct { + mock.Mock +} + +func (m *NewsFileGCSAPIMock) Delete(filePaths []string) error { + args := m.Called(filePaths) + return args.Error(0) +} + +func (m *NewsFileGCSAPIMock) Upload(files []*multipart.FileHeader) ([]string, error) { + args := m.Called(files) + return args.Get(0).([]string), args.Error(1) +} + +func TestNewsFileUseCase_Create(t *testing.T) { + t.Run("success", func(t *testing.T) { + newsFileMock := new(NewsFileMock) + newsFileGCSAPIMock := new(NewsFileGCSAPIMock) + newsFileUseCase := NewNewsFileUseCase(newsFileMock, newsFileGCSAPIMock) + + files := []*multipart.FileHeader{ + &multipart.FileHeader{}, + &multipart.FileHeader{}, + } + newsID := 1 + + newsFileGCSAPIMock.On("Upload", files).Return([]string{"path1", "path2"}, nil) + newsFileMock.On("Create", mock.Anything).Return(nil) + + _, err := newsFileUseCase.Create(files, newsID) + assert.Nil(t, err) + }) + + t.Run("upload error", func(t *testing.T) { + newsFileMock := new(NewsFileMock) + newsFileGCSAPIMock := new(NewsFileGCSAPIMock) + newsFileUseCase := NewNewsFileUseCase(newsFileMock, newsFileGCSAPIMock) + + files := []*multipart.FileHeader{ + &multipart.FileHeader{}, + &multipart.FileHeader{}, + } + newsID := 1 + + newsFileGCSAPIMock.On("Upload", files).Return([]string{}, errors.New("upload error")) + + _, err := newsFileUseCase.Create(files, newsID) + assert.NotNil(t, err) + }) + + t.Run("create error", func(t *testing.T) { + newsFileMock := new(NewsFileMock) + newsFileGCSAPIMock := new(NewsFileGCSAPIMock) + newsFileUseCase := NewNewsFileUseCase(newsFileMock, newsFileGCSAPIMock) + + files := []*multipart.FileHeader{ + &multipart.FileHeader{}, + &multipart.FileHeader{}, + } + newsID := 1 + + newsFileGCSAPIMock.On("Upload", files).Return([]string{"path1", "path2"}, nil) + newsFileMock.On("Create", mock.Anything).Return(errors.New("create error")) + + _, err := newsFileUseCase.Create(files, newsID) + assert.NotNil(t, err) + }) +} + +func TestNewsFileUseCase_DeleteByNewsID(t *testing.T) { + t.Run("success", func(t *testing.T) { + newsFileMock := new(NewsFileMock) + newsFileGCSAPIMock := new(NewsFileGCSAPIMock) + newsFileUseCase := NewNewsFileUseCase(newsFileMock, newsFileGCSAPIMock) + + newsID := 1 + + newsFileMock.On("DeleteByNewsID", newsID).Return(nil) + newsFileGCSAPIMock.On("Delete", mock.Anything).Return(nil) + + err := newsFileUseCase.DeleteByNewsID(newsID) + assert.Nil(t, err) + }) + + t.Run("delete error", func(t *testing.T) { + newsFileMock := new(NewsFileMock) + newsFileGCSAPIMock := new(NewsFileGCSAPIMock) + newsFileUseCase := NewNewsFileUseCase(newsFileMock, newsFileGCSAPIMock) + + newsID := 1 + + newsFileMock.On("DeleteByNewsID", newsID).Return(errors.New("delete error")) + + err := newsFileUseCase.DeleteByNewsID(newsID) + assert.NotNil(t, err) + }) +} diff --git a/usecases/news_like/news_like.go b/usecases/news_like/news_like.go index 4e0b197..69e96bf 100644 --- a/usecases/news_like/news_like.go +++ b/usecases/news_like/news_like.go @@ -12,25 +12,8 @@ func NewNewsLikeUseCase(repo entities.NewsLikeRepositoryInterface) *NewsLikeUseC } } -func (u *NewsLikeUseCase) LikeNews(userID int, newsID int) error { - newsLike := entities.NewsLike{ - UserID: userID, - NewsID: newsID, - } - - err := u.repo.Likes(&newsLike) - if err != nil { - return err - } - - return nil -} - func (u *NewsLikeUseCase) ToggleLike(newsLike *entities.NewsLike) (string, error) { like, err := u.repo.FindByUserAndNews(newsLike.UserID, newsLike.NewsID) - if err != nil { - return "", err - } if like == nil { err := u.repo.Likes(newsLike) @@ -49,33 +32,6 @@ func (u *NewsLikeUseCase) ToggleLike(newsLike *entities.NewsLike) (string, error return "unliked", nil } -func (u *NewsLikeUseCase) UnlikeNews(userID int, newsID int) error { - newsLike, err := u.repo.FindByUserAndNews(userID, newsID) - if err != nil { - return err - } - - if newsLike == nil { - return nil - } - - err = u.repo.Unlike(newsLike) - if err != nil { - return err - } - - return nil -} - -func (u *NewsLikeUseCase) FindByUserAndNews(userID int, newsID int) (*entities.NewsLike, error) { - newsLike, err := u.repo.FindByUserAndNews(userID, newsID) - if err != nil { - return nil, err - } - - return newsLike, nil -} - func (u *NewsLikeUseCase) IncreaseTotalLikes(id string) error { err := u.repo.IncreaseTotalLikes(id) if err != nil { diff --git a/usecases/news_like/news_like_test.go b/usecases/news_like/news_like_test.go new file mode 100644 index 0000000..beea8b5 --- /dev/null +++ b/usecases/news_like/news_like_test.go @@ -0,0 +1,197 @@ +package news_like + +import ( + "e-complaint-api/entities" + "errors" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "testing" +) + +type MockNewsLike struct { + mock.Mock +} + +func (m *MockNewsLike) FindByUserAndNews(userID int, newsID int) (*entities.NewsLike, error) { + args := m.Called(userID, newsID) + return args.Get(0).(*entities.NewsLike), args.Error(1) +} + +func (m *MockNewsLike) Likes(newsLike *entities.NewsLike) error { + args := m.Called(newsLike) + return args.Error(0) +} + +func (m *MockNewsLike) Unlike(newsLike *entities.NewsLike) error { + args := m.Called(newsLike) + return args.Error(0) +} + +func (m *MockNewsLike) ToggleLike(newsLike *entities.NewsLike) (string, error) { + args := m.Called(newsLike) + return args.String(0), args.Error(1) +} + +func (m *MockNewsLike) IncreaseTotalLikes(id string) error { + args := m.Called(id) + return args.Error(0) +} + +func (m *MockNewsLike) DecreaseTotalLikes(id string) error { + args := m.Called(id) + return args.Error(0) +} + +func TestNewsLikeUseCase_ToggleLike(t *testing.T) { + t.Run("success", func(t *testing.T) { + mockNewsLike := new(MockNewsLike) + useCase := NewNewsLikeUseCase(mockNewsLike) + newsLike := &entities.NewsLike{ + UserID: 1, + NewsID: 1, + } + mockNewsLike.On("FindByUserAndNews", newsLike.UserID, newsLike.NewsID).Return(newsLike, nil) + mockNewsLike.On("Unlike", newsLike).Return(nil) + result, err := useCase.ToggleLike(newsLike) + + assert.NoError(t, err) + assert.Equal(t, "unliked", result) + mockNewsLike.AssertExpectations(t) + }) + + t.Run("failed", func(t *testing.T) { + mockNewsLike := new(MockNewsLike) + useCase := NewNewsLikeUseCase(mockNewsLike) + newsLike := &entities.NewsLike{ + UserID: 1, + NewsID: 1, + } + mockNewsLike.On("FindByUserAndNews", newsLike.UserID, newsLike.NewsID).Return((*entities.NewsLike)(nil), nil) + mockNewsLike.On("Likes", newsLike).Return(nil) + result, err := useCase.ToggleLike(newsLike) + + assert.NoError(t, err) + assert.Equal(t, "liked", result) + mockNewsLike.AssertExpectations(t) + }) + + t.Run("unlike", func(t *testing.T) { + mockRepo := new(MockNewsLike) + useCase := NewNewsLikeUseCase(mockRepo) + newsLike := &entities.NewsLike{ + UserID: 1, + NewsID: 1, + } + + mockRepo.On("FindByUserAndNews", newsLike.UserID, newsLike.NewsID).Return(newsLike, nil) + mockRepo.On("Unlike", newsLike).Return(nil) + + result, err := useCase.ToggleLike(newsLike) + + assert.NoError(t, err) + assert.Equal(t, "unliked", result) + mockRepo.AssertExpectations(t) + }) + + t.Run("like", func(t *testing.T) { + mockRepo := new(MockNewsLike) + useCase := NewNewsLikeUseCase(mockRepo) + newsLike := &entities.NewsLike{ + UserID: 1, + NewsID: 1, + } + + mockRepo.On("FindByUserAndNews", newsLike.UserID, newsLike.NewsID).Return((*entities.NewsLike)(nil), nil) + mockRepo.On("Likes", newsLike).Return(nil) + + result, err := useCase.ToggleLike(newsLike) + + assert.NoError(t, err) + assert.Equal(t, "liked", result) + mockRepo.AssertExpectations(t) + }) + + t.Run("like error", func(t *testing.T) { + mockRepo := new(MockNewsLike) + useCase := NewNewsLikeUseCase(mockRepo) + newsLike := &entities.NewsLike{ + UserID: 1, + NewsID: 1, + } + + mockRepo.On("FindByUserAndNews", newsLike.UserID, newsLike.NewsID).Return((*entities.NewsLike)(nil), nil) + mockRepo.On("Likes", newsLike).Return(errors.New("error")) + + _, err := useCase.ToggleLike(newsLike) + + assert.Error(t, err) + mockRepo.AssertExpectations(t) + }) + + t.Run("unlike error", func(t *testing.T) { + mockRepo := new(MockNewsLike) + useCase := NewNewsLikeUseCase(mockRepo) + newsLike := &entities.NewsLike{ + UserID: 1, + NewsID: 1, + } + + mockRepo.On("FindByUserAndNews", newsLike.UserID, newsLike.NewsID).Return(newsLike, nil) + mockRepo.On("Unlike", newsLike).Return(errors.New("error")) + + _, err := useCase.ToggleLike(newsLike) + + assert.Error(t, err) + mockRepo.AssertExpectations(t) + }) + +} + +func TestNewsLikeUseCase_IncreaseTotalLikes(t *testing.T) { + t.Run("success", func(t *testing.T) { + mockNewsLike := new(MockNewsLike) + useCase := NewNewsLikeUseCase(mockNewsLike) + id := "1" + mockNewsLike.On("IncreaseTotalLikes", id).Return(nil) + err := useCase.IncreaseTotalLikes(id) + + assert.NoError(t, err) + mockNewsLike.AssertExpectations(t) + }) + + t.Run("failed", func(t *testing.T) { + mockNewsLike := new(MockNewsLike) + useCase := NewNewsLikeUseCase(mockNewsLike) + id := "1" + mockNewsLike.On("IncreaseTotalLikes", id).Return(errors.New("error")) + err := useCase.IncreaseTotalLikes(id) + + assert.Error(t, err) + mockNewsLike.AssertExpectations(t) + }) + +} + +func TestNewsLikeUseCase_DecreaseTotalLikes(t *testing.T) { + t.Run("success", func(t *testing.T) { + mockNewsLike := new(MockNewsLike) + useCase := NewNewsLikeUseCase(mockNewsLike) + id := "1" + mockNewsLike.On("DecreaseTotalLikes", id).Return(nil) + err := useCase.DecreaseTotalLikes(id) + + assert.NoError(t, err) + mockNewsLike.AssertExpectations(t) + }) + + t.Run("failed", func(t *testing.T) { + mockNewsLike := new(MockNewsLike) + useCase := NewNewsLikeUseCase(mockNewsLike) + id := "1" + mockNewsLike.On("DecreaseTotalLikes", id).Return(errors.New("error")) + err := useCase.DecreaseTotalLikes(id) + + assert.Error(t, err) + mockNewsLike.AssertExpectations(t) + }) +} diff --git a/usecases/regency/regency_test.go b/usecases/regency/regency_test.go new file mode 100644 index 0000000..870d90e --- /dev/null +++ b/usecases/regency/regency_test.go @@ -0,0 +1,58 @@ +package regency + +import ( + "e-complaint-api/constants" + "e-complaint-api/entities" + "errors" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +type RegencyRepositoryMock struct { + mock.Mock +} + +func (m *RegencyRepositoryMock) GetAll() ([]entities.Regency, error) { + args := m.Called() + return args.Get(0).([]entities.Regency), args.Error(1) +} + +func TestRegencyUseCase_GetAll(t *testing.T) { + t.Run("success", func(t *testing.T) { + repo := new(RegencyRepositoryMock) + uc := NewRegencyUseCase(repo) + + regencies := []entities.Regency{ + { + ID: "1901", + Name: "Kabupaten Bogor", + }, + { + ID: "1902", + Name: "Kabupaten Bekasi", + }, + } + + repo.On("GetAll").Return(regencies, nil) + + result, err := uc.GetAll() + + assert.NoError(t, err) + assert.Equal(t, regencies, result) + }) + + t.Run("failed internal server error", func(t *testing.T) { + repo := new(RegencyRepositoryMock) + uc := NewRegencyUseCase(repo) + + repo.On("GetAll").Return([]entities.Regency{}, errors.New("unexpected error")) + + result, err := uc.GetAll() + + assert.Error(t, err) + assert.Equal(t, constants.ErrInternalServerError, err) + assert.Empty(t, result) + }) +} diff --git a/usecases/user/user.go b/usecases/user/user.go index 6e58569..367f9f9 100644 --- a/usecases/user/user.go +++ b/usecases/user/user.go @@ -29,6 +29,10 @@ func (u *UserUseCase) Register(user *entities.User) (entities.User, error) { return entities.User{}, constants.ErrAllFieldsMustBeFilled } + if len(user.Password) < 8 { + return entities.User{}, constants.ErrPasswordMustBeAtLeast8Characters + } + err := u.repository.Register(user) if err != nil { @@ -76,29 +80,25 @@ func (u *UserUseCase) GetUserByID(id int) (*entities.User, error) { user, err := u.repository.GetUserByID(id) if err != nil { - return nil, constants.ErrInternalServerError + return nil, err } return user, nil } func (u *UserUseCase) UpdateUser(id int, user *entities.User) (entities.User, error) { - existingUser, err := u.repository.GetUserByID(id) - if err != nil { - return entities.User{}, constants.ErrInternalServerError + if user.Email == "" || user.Name == "" || user.TelephoneNumber == "" { + return entities.User{}, constants.ErrAllFieldsMustBeFilled } - // Ensure existing data remains if no new data is provided - if user.Name != "" { - existingUser.Name = user.Name - } - if user.Email != "" { - existingUser.Email = user.Email + existingUser, err := u.repository.GetUserByID(id) + if err != nil { + return entities.User{}, err } - if user.TelephoneNumber != "" { - existingUser.TelephoneNumber = user.TelephoneNumber - } + existingUser.Name = user.Name + existingUser.Email = user.Email + existingUser.TelephoneNumber = user.TelephoneNumber err = u.repository.UpdateUser(id, existingUser) if err != nil { @@ -131,18 +131,14 @@ func (u *UserUseCase) UpdateProfilePhoto(id int, profilePhoto *multipart.FileHea } func (u *UserUseCase) Delete(id int) error { - existingUser, err := u.repository.GetUserByID(id) + _, err := u.repository.GetUserByID(id) if err != nil { - if errors.Is(err, constants.ErrNotFound) { - return constants.ErrNotFound + if errors.Is(err, constants.ErrUserNotFound) { + return constants.ErrUserNotFound } return constants.ErrInternalServerError } - if existingUser == nil { - return constants.ErrNotFound - } - err = u.repository.Delete(id) if err != nil { return constants.ErrInternalServerError @@ -151,13 +147,13 @@ func (u *UserUseCase) Delete(id int) error { return nil } -func (u *UserUseCase) UpdatePassword(id int, newPassword, confirmNewPassword string) error { - if newPassword == "" || confirmNewPassword == "" { +func (u *UserUseCase) UpdatePassword(id int, newPassword string) error { + if newPassword == "" { return constants.ErrAllFieldsMustBeFilled } - if newPassword != confirmNewPassword { - return constants.ErrConfirmPasswordDoesntMatch + if len(newPassword) < 8 { + return constants.ErrPasswordMustBeAtLeast8Characters } hash, _ := utils.HashPassword(newPassword) @@ -209,6 +205,10 @@ func (u *UserUseCase) UpdatePasswordForgot(email, newPassword string) error { return constants.ErrAllFieldsMustBeFilled } + if len(newPassword) < 8 { + return constants.ErrPasswordMustBeAtLeast8Characters + } + hash, _ := utils.HashPassword(newPassword) err := u.repository.UpdatePasswordForgot(email, hash) if err != nil { diff --git a/usecases/user/user_test.go b/usecases/user/user_test.go new file mode 100644 index 0000000..07468f8 --- /dev/null +++ b/usecases/user/user_test.go @@ -0,0 +1,972 @@ +package user + +import ( + "e-complaint-api/constants" + "e-complaint-api/entities" + "errors" + "mime/multipart" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +type MockUserRepository struct { + mock.Mock +} + +func (m *MockUserRepository) Register(user *entities.User) error { + args := m.Called(user) + return args.Error(0) +} + +func (m *MockUserRepository) Login(user *entities.User) error { + args := m.Called(user) + return args.Error(0) +} + +func (m *MockUserRepository) GetAllUsers() ([]*entities.User, error) { + args := m.Called() + return args.Get(0).([]*entities.User), args.Error(1) +} + +func (m *MockUserRepository) GetUserByID(id int) (*entities.User, error) { + args := m.Called(id) + return args.Get(0).(*entities.User), args.Error(1) +} + +func (m *MockUserRepository) UpdateUser(id int, user *entities.User) error { + args := m.Called(id, user) + return args.Error(0) +} + +func (m *MockUserRepository) UpdateProfilePhoto(id int, photo string) error { + args := m.Called(id, photo) + return args.Error(0) +} + +func (m *MockUserRepository) Delete(id int) error { + args := m.Called(id) + return args.Error(0) +} + +func (m *MockUserRepository) UpdatePassword(id int, newPassword string) error { + args := m.Called(id, newPassword) + return args.Error(0) +} + +func (m *MockUserRepository) SendOTP(email, otp string) error { + args := m.Called(email, otp) + return args.Error(0) +} + +func (m *MockUserRepository) VerifyOTPRegister(email, otp string) error { + args := m.Called(email, otp) + return args.Error(0) +} + +func (m *MockUserRepository) VerifyOTPForgotPassword(email, otp string) error { + args := m.Called(email, otp) + return args.Error(0) +} + +func (m *MockUserRepository) UpdatePasswordForgot(email, newPassword string) error { + args := m.Called(email, newPassword) + return args.Error(0) +} + +type MockMailTrapAPI struct { + mock.Mock +} + +func (m *MockMailTrapAPI) SendOTP(email, otp, otpType string) error { + args := m.Called(email, otp, otpType) + return args.Error(0) +} + +type MockUserGCSAPI struct { + mock.Mock +} + +func (m *MockUserGCSAPI) Upload(files []*multipart.FileHeader) ([]string, error) { + args := m.Called(files) + return args.Get(0).([]string), args.Error(1) +} + +func (m *MockUserGCSAPI) Delete(filePaths []string) error { + args := m.Called(filePaths) + return args.Error(0) +} + +func TestRegister(t *testing.T) { + t.Run("success", func(t *testing.T) { + mockUserRepository := new(MockUserRepository) + mockMailTrapAPI := new(MockMailTrapAPI) + mockUserGCSAPI := new(MockUserGCSAPI) + userUseCase := NewUserUseCase(mockUserRepository, mockMailTrapAPI, mockUserGCSAPI) + + user := entities.User{ + Email: "user@example.com", + Password: "password", + Name: "User", + TelephoneNumber: "081234567890", + } + + mockUserRepository.On("Register", &user).Return(nil) + + result, err := userUseCase.Register(&user) + assert.NoError(t, err) + assert.Equal(t, &result, &user) + + mockUserRepository.AssertExpectations(t) + }) + + t.Run("failed empty field", func(t *testing.T) { + mockUserRepository := new(MockUserRepository) + mockMailTrapAPI := new(MockMailTrapAPI) + mockUserGCSAPI := new(MockUserGCSAPI) + userUseCase := NewUserUseCase(mockUserRepository, mockMailTrapAPI, mockUserGCSAPI) + + user := entities.User{ + Email: "", + Password: "password", + Name: "User", + TelephoneNumber: "081234567890", + } + + result, err := userUseCase.Register(&user) + assert.Error(t, constants.ErrAllFieldsMustBeFilled, err) + assert.Equal(t, entities.User{}, result) + + mockUserRepository.AssertExpectations(t) + }) + + t.Run("failed email already exists", func(t *testing.T) { + mockUserRepository := new(MockUserRepository) + mockMailTrapAPI := new(MockMailTrapAPI) + mockUserGCSAPI := new(MockUserGCSAPI) + userUseCase := NewUserUseCase(mockUserRepository, mockMailTrapAPI, mockUserGCSAPI) + + user := entities.User{ + Email: "user@gmail.com", + Password: "password", + Name: "User", + TelephoneNumber: "081234567890", + } + + mockUserRepository.On("Register", &user).Return(errors.New("Error 1062 email'")) + + result, err := userUseCase.Register(&user) + assert.Error(t, constants.ErrEmailAlreadyExists, err) + assert.Equal(t, entities.User{}, result) + + mockUserRepository.AssertExpectations(t) + }) + + t.Run("failed username already exists", func(t *testing.T) { + mockUserRepository := new(MockUserRepository) + mockMailTrapAPI := new(MockMailTrapAPI) + mockUserGCSAPI := new(MockUserGCSAPI) + userUseCase := NewUserUseCase(mockUserRepository, mockMailTrapAPI, mockUserGCSAPI) + + user := entities.User{ + Email: "user@gmail.com", + Password: "password", + Name: "User", + TelephoneNumber: "081234567890", + } + + mockUserRepository.On("Register", &user).Return(errors.New("Error 1062 username'")) + + result, err := userUseCase.Register(&user) + assert.Error(t, constants.ErrUsernameAlreadyExists, err) + assert.Equal(t, entities.User{}, result) + + mockUserRepository.AssertExpectations(t) + }) + + t.Run("failed internal server error", func(t *testing.T) { + mockUserRepository := new(MockUserRepository) + mockMailTrapAPI := new(MockMailTrapAPI) + mockUserGCSAPI := new(MockUserGCSAPI) + userUseCase := NewUserUseCase(mockUserRepository, mockMailTrapAPI, mockUserGCSAPI) + + user := entities.User{ + Email: "user@gmail.com", + Password: "password", + Name: "User", + TelephoneNumber: "081234567890", + } + + mockUserRepository.On("Register", &user).Return(constants.ErrInternalServerError) + + result, err := userUseCase.Register(&user) + assert.Error(t, constants.ErrInternalServerError, err) + assert.Equal(t, entities.User{}, result) + + mockUserRepository.AssertExpectations(t) + }) + + t.Run("failed password must be at least 8 characters", func(t *testing.T) { + mockUserRepository := new(MockUserRepository) + mockMailTrapAPI := new(MockMailTrapAPI) + mockUserGCSAPI := new(MockUserGCSAPI) + userUseCase := NewUserUseCase(mockUserRepository, mockMailTrapAPI, mockUserGCSAPI) + + user := entities.User{ + Email: "user@gmail.com", + Password: "pass", + Name: "User", + TelephoneNumber: "081234567890", + } + + result, err := userUseCase.Register(&user) + assert.Error(t, constants.ErrPasswordMustBeAtLeast8Characters, err) + assert.Equal(t, entities.User{}, result) + + mockUserRepository.AssertExpectations(t) + }) +} + +func TestLogin(t *testing.T) { + t.Run("success", func(t *testing.T) { + mockUserRepository := new(MockUserRepository) + mockMailTrapAPI := new(MockMailTrapAPI) + mockUserGCSAPI := new(MockUserGCSAPI) + userUseCase := NewUserUseCase(mockUserRepository, mockMailTrapAPI, mockUserGCSAPI) + + user := entities.User{ + Email: "user@gmail.com", + Password: "password", + } + + mockUserRepository.On("Login", &user).Return(nil) + + result, err := userUseCase.Login(&user) + assert.NoError(t, err) + assert.NotEmpty(t, result.Token) + + mockUserRepository.AssertExpectations(t) + }) + + t.Run("failed empty field", func(t *testing.T) { + mockUserRepository := new(MockUserRepository) + mockMailTrapAPI := new(MockMailTrapAPI) + mockUserGCSAPI := new(MockUserGCSAPI) + userUseCase := NewUserUseCase(mockUserRepository, mockMailTrapAPI, mockUserGCSAPI) + + user := entities.User{ + Email: "", + Password: "password", + } + + result, err := userUseCase.Login(&user) + assert.Error(t, constants.ErrAllFieldsMustBeFilled, err) + assert.Equal(t, entities.User{}, result) + + mockUserRepository.AssertExpectations(t) + }) + + t.Run("failed invalid username or password", func(t *testing.T) { + mockUserRepository := new(MockUserRepository) + mockMailTrapAPI := new(MockMailTrapAPI) + mockUserGCSAPI := new(MockUserGCSAPI) + userUseCase := NewUserUseCase(mockUserRepository, mockMailTrapAPI, mockUserGCSAPI) + + user := entities.User{ + Email: "user123@gmail.com", + Password: "password", + } + + mockUserRepository.On("Login", &user).Return(constants.ErrInvalidUsernameOrPassword) + + result, err := userUseCase.Login(&user) + assert.Error(t, constants.ErrInvalidUsernameOrPassword, err) + assert.Equal(t, entities.User{}, result) + }) +} + +func TestGetAllUsers(t *testing.T) { + t.Run("success", func(t *testing.T) { + mockUserRepository := new(MockUserRepository) + mockMailTrapAPI := new(MockMailTrapAPI) + mockUserGCSAPI := new(MockUserGCSAPI) + userUseCase := NewUserUseCase(mockUserRepository, mockMailTrapAPI, mockUserGCSAPI) + + users := []*entities.User{ + { + ID: 1, + Email: "user1@gmail.com", + Name: "User 1", + TelephoneNumber: "081234567890", + ProfilePhoto: "profile_photo.jpg", + }, + { + ID: 2, + Email: "user2@gmail.com", + Name: "User 2", + TelephoneNumber: "081234567891", + ProfilePhoto: "profile_photo.jpg", + }, + } + + mockUserRepository.On("GetAllUsers").Return(users, nil) + + result, err := userUseCase.GetAllUsers() + assert.NoError(t, err) + assert.Equal(t, users, result) + + mockUserRepository.AssertExpectations(t) + }) + + t.Run("failed internal server error", func(t *testing.T) { + mockUserRepository := new(MockUserRepository) + mockMailTrapAPI := new(MockMailTrapAPI) + mockUserGCSAPI := new(MockUserGCSAPI) + userUseCase := NewUserUseCase(mockUserRepository, mockMailTrapAPI, mockUserGCSAPI) + + mockUserRepository.On("GetAllUsers").Return(([]*entities.User)(nil), constants.ErrInternalServerError) + + result, err := userUseCase.GetAllUsers() + assert.Error(t, constants.ErrInternalServerError, err) + assert.Nil(t, result) + + mockUserRepository.AssertExpectations(t) + }) +} + +func TestGetUserByID(t *testing.T) { + t.Run("success", func(t *testing.T) { + mockUserRepository := new(MockUserRepository) + mockMailTrapAPI := new(MockMailTrapAPI) + mockUserGCSAPI := new(MockUserGCSAPI) + userUseCase := NewUserUseCase(mockUserRepository, mockMailTrapAPI, mockUserGCSAPI) + + user := entities.User{ + ID: 1, + Email: "user1@gmail.com", + Name: "User 1", + TelephoneNumber: "081234567890", + ProfilePhoto: "profile_photo.jpg", + } + + mockUserRepository.On("GetUserByID", 1).Return(&user, nil) + + result, err := userUseCase.GetUserByID(1) + assert.NoError(t, err) + assert.Equal(t, &user, result) + + mockUserRepository.AssertExpectations(t) + }) + + t.Run("failed user not found", func(t *testing.T) { + mockUserRepository := new(MockUserRepository) + mockMailTrapAPI := new(MockMailTrapAPI) + mockUserGCSAPI := new(MockUserGCSAPI) + userUseCase := NewUserUseCase(mockUserRepository, mockMailTrapAPI, mockUserGCSAPI) + + mockUserRepository.On("GetUserByID", 1).Return((*entities.User)(nil), constants.ErrUserNotFound) + + result, err := userUseCase.GetUserByID(1) + assert.Error(t, constants.ErrUserNotFound, err) + assert.Nil(t, result) + + mockUserRepository.AssertExpectations(t) + }) +} + +func TestUpdateUser(t *testing.T) { + t.Run("success", func(t *testing.T) { + mockUserRepository := new(MockUserRepository) + mockMailTrapAPI := new(MockMailTrapAPI) + mockUserGCSAPI := new(MockUserGCSAPI) + userUseCase := NewUserUseCase(mockUserRepository, mockMailTrapAPI, mockUserGCSAPI) + + user := entities.User{ + ID: 1, + Email: "user@gmail.com", + Name: "User", + TelephoneNumber: "081234567890", + } + + mockUserRepository.On("GetUserByID", 1).Return(&user, nil) + mockUserRepository.On("UpdateUser", 1, &user).Return(nil) + + result, err := userUseCase.UpdateUser(1, &user) + assert.NoError(t, err) + assert.Equal(t, user, result) + + mockUserRepository.AssertExpectations(t) + }) + + t.Run("failed empty field", func(t *testing.T) { + mockUserRepository := new(MockUserRepository) + mockMailTrapAPI := new(MockMailTrapAPI) + mockUserGCSAPI := new(MockUserGCSAPI) + userUseCase := NewUserUseCase(mockUserRepository, mockMailTrapAPI, mockUserGCSAPI) + + user := entities.User{ + ID: 1, + Email: "", + Name: "User", + TelephoneNumber: "081234567890", + } + + result, err := userUseCase.UpdateUser(1, &user) + assert.Error(t, constants.ErrAllFieldsMustBeFilled, err) + assert.Equal(t, entities.User{}, result) + + mockUserRepository.AssertExpectations(t) + }) + + t.Run("failed user not found", func(t *testing.T) { + mockUserRepository := new(MockUserRepository) + mockMailTrapAPI := new(MockMailTrapAPI) + mockUserGCSAPI := new(MockUserGCSAPI) + userUseCase := NewUserUseCase(mockUserRepository, mockMailTrapAPI, mockUserGCSAPI) + + user := entities.User{ + ID: 1, + Email: "user@gmail.com", + Name: "User", + TelephoneNumber: "081234567890", + } + + mockUserRepository.On("GetUserByID", 1).Return((*entities.User)(nil), constants.ErrUserNotFound) + + result, err := userUseCase.UpdateUser(1, &user) + assert.Error(t, constants.ErrUserNotFound, err) + assert.Equal(t, entities.User{}, result) + + mockUserRepository.AssertExpectations(t) + }) + + t.Run("failed internal server error", func(t *testing.T) { + mockUserRepository := new(MockUserRepository) + mockMailTrapAPI := new(MockMailTrapAPI) + mockUserGCSAPI := new(MockUserGCSAPI) + userUseCase := NewUserUseCase(mockUserRepository, mockMailTrapAPI, mockUserGCSAPI) + + user := entities.User{ + ID: 1, + Email: "user@gmail.com", + Name: "User", + TelephoneNumber: "081234567890", + } + + mockUserRepository.On("GetUserByID", 1).Return(&user, nil) + mockUserRepository.On("UpdateUser", 1, &user).Return(constants.ErrInternalServerError) + + result, err := userUseCase.UpdateUser(1, &user) + assert.Error(t, constants.ErrInternalServerError, err) + assert.Equal(t, entities.User{}, result) + + mockUserRepository.AssertExpectations(t) + }) + + t.Run("failed email already exists", func(t *testing.T) { + mockUserRepository := new(MockUserRepository) + mockMailTrapAPI := new(MockMailTrapAPI) + mockUserGCSAPI := new(MockUserGCSAPI) + userUseCase := NewUserUseCase(mockUserRepository, mockMailTrapAPI, mockUserGCSAPI) + + user := entities.User{ + ID: 1, + Email: "user@gmail.com", + Name: "User", + TelephoneNumber: "081234567890", + } + + mockUserRepository.On("GetUserByID", 1).Return(&user, nil) + mockUserRepository.On("UpdateUser", 1, &user).Return(errors.New("Error 1062 email'")) + + result, err := userUseCase.UpdateUser(1, &user) + assert.Error(t, constants.ErrEmailAlreadyExists, err) + assert.Equal(t, entities.User{}, result) + + mockUserRepository.AssertExpectations(t) + }) + + t.Run("failed username already exists", func(t *testing.T) { + mockUserRepository := new(MockUserRepository) + mockMailTrapAPI := new(MockMailTrapAPI) + mockUserGCSAPI := new(MockUserGCSAPI) + userUseCase := NewUserUseCase(mockUserRepository, mockMailTrapAPI, mockUserGCSAPI) + + user := entities.User{ + ID: 1, + Email: "user@gmail.com", + Name: "User", + TelephoneNumber: "081234567890", + } + + mockUserRepository.On("GetUserByID", 1).Return(&user, nil) + mockUserRepository.On("UpdateUser", 1, &user).Return(errors.New("Error 1062 username'")) + + result, err := userUseCase.UpdateUser(1, &user) + assert.Error(t, constants.ErrUsernameAlreadyExists, err) + assert.Equal(t, entities.User{}, result) + + mockUserRepository.AssertExpectations(t) + }) + +} + +func TestUpdateProfilePhoto(t *testing.T) { + t.Run("success", func(t *testing.T) { + mockUserRepository := new(MockUserRepository) + mockMailTrapAPI := new(MockMailTrapAPI) + mockUserGCSAPI := new(MockUserGCSAPI) + userUseCase := NewUserUseCase(mockUserRepository, mockMailTrapAPI, mockUserGCSAPI) + + fileHeader := &multipart.FileHeader{ + Filename: "profile_photo.jpg", + } + + fileHeaders := []*multipart.FileHeader{fileHeader} + + mockUserGCSAPI.On("Upload", fileHeaders).Return([]string{"profile_photo.jpg"}, nil) + mockUserRepository.On("UpdateProfilePhoto", 1, "profile_photo.jpg").Return(nil) + + err := userUseCase.UpdateProfilePhoto(1, fileHeader) + assert.NoError(t, err) + + mockUserRepository.AssertExpectations(t) + }) + + t.Run("failed gcs internal server error", func(t *testing.T) { + mockUserRepository := new(MockUserRepository) + mockMailTrapAPI := new(MockMailTrapAPI) + mockUserGCSAPI := new(MockUserGCSAPI) + userUseCase := NewUserUseCase(mockUserRepository, mockMailTrapAPI, mockUserGCSAPI) + + fileHeader := &multipart.FileHeader{ + Filename: "profile_photo.jpg", + } + + fileHeaders := []*multipart.FileHeader{fileHeader} + + mockUserGCSAPI.On("Upload", fileHeaders).Return(([]string)(nil), constants.ErrInternalServerError) + + err := userUseCase.UpdateProfilePhoto(1, fileHeader) + assert.Error(t, constants.ErrInternalServerError, err) + + mockUserRepository.AssertExpectations(t) + }) + + t.Run("failed internal server error", func(t *testing.T) { + mockUserRepository := new(MockUserRepository) + mockMailTrapAPI := new(MockMailTrapAPI) + mockUserGCSAPI := new(MockUserGCSAPI) + userUseCase := NewUserUseCase(mockUserRepository, mockMailTrapAPI, mockUserGCSAPI) + + fileHeader := &multipart.FileHeader{ + Filename: "profile_photo.jpg", + } + + fileHeaders := []*multipart.FileHeader{fileHeader} + + mockUserGCSAPI.On("Upload", fileHeaders).Return([]string{"profile_photo.jpg"}, nil) + mockUserRepository.On("UpdateProfilePhoto", 1, "profile_photo.jpg").Return(constants.ErrInternalServerError) + + err := userUseCase.UpdateProfilePhoto(1, fileHeader) + assert.Error(t, constants.ErrInternalServerError, err) + + mockUserRepository.AssertExpectations(t) + }) +} + +func TestDelete(t *testing.T) { + t.Run("success", func(t *testing.T) { + mockUserRepository := new(MockUserRepository) + mockMailTrapAPI := new(MockMailTrapAPI) + mockUserGCSAPI := new(MockUserGCSAPI) + userUseCase := NewUserUseCase(mockUserRepository, mockMailTrapAPI, mockUserGCSAPI) + + mockUserRepository.On("GetUserByID", 1).Return(&entities.User{}, nil) + mockUserRepository.On("Delete", 1).Return(nil) + + err := userUseCase.Delete(1) + assert.NoError(t, err) + + mockUserRepository.AssertExpectations(t) + }) + + t.Run("failed internal server error", func(t *testing.T) { + mockUserRepository := new(MockUserRepository) + mockMailTrapAPI := new(MockMailTrapAPI) + mockUserGCSAPI := new(MockUserGCSAPI) + userUseCase := NewUserUseCase(mockUserRepository, mockMailTrapAPI, mockUserGCSAPI) + + mockUserRepository.On("GetUserByID", 1).Return(&entities.User{}, nil) + mockUserRepository.On("Delete", 1).Return(constants.ErrInternalServerError) + + err := userUseCase.Delete(1) + assert.Error(t, constants.ErrInternalServerError, err) + + mockUserRepository.AssertExpectations(t) + }) + + t.Run("failed internal server error get user by id", func(t *testing.T) { + mockUserRepository := new(MockUserRepository) + mockMailTrapAPI := new(MockMailTrapAPI) + mockUserGCSAPI := new(MockUserGCSAPI) + userUseCase := NewUserUseCase(mockUserRepository, mockMailTrapAPI, mockUserGCSAPI) + + mockUserRepository.On("GetUserByID", 1).Return((*entities.User)(nil), constants.ErrInternalServerError) + + err := userUseCase.Delete(1) + assert.Error(t, constants.ErrInternalServerError, err) + + mockUserRepository.AssertExpectations(t) + }) + + t.Run("failed user not found", func(t *testing.T) { + mockUserRepository := new(MockUserRepository) + mockMailTrapAPI := new(MockMailTrapAPI) + mockUserGCSAPI := new(MockUserGCSAPI) + userUseCase := NewUserUseCase(mockUserRepository, mockMailTrapAPI, mockUserGCSAPI) + + mockUserRepository.On("GetUserByID", 1).Return((*entities.User)(nil), constants.ErrUserNotFound) + + err := userUseCase.Delete(1) + assert.Error(t, constants.ErrUserNotFound, err) + + mockUserRepository.AssertExpectations(t) + }) + +} + +func TestUpdatePassword(t *testing.T) { + t.Run("success", func(t *testing.T) { + mockUserRepository := new(MockUserRepository) + mockMailTrapAPI := new(MockMailTrapAPI) + mockUserGCSAPI := new(MockUserGCSAPI) + userUseCase := NewUserUseCase(mockUserRepository, mockMailTrapAPI, mockUserGCSAPI) + + mockUserRepository.On("UpdatePassword", 1, mock.Anything).Return(nil) + + err := userUseCase.UpdatePassword(1, "password") + assert.NoError(t, err) + + mockUserRepository.AssertExpectations(t) + }) + + t.Run("failed empty field", func(t *testing.T) { + mockUserRepository := new(MockUserRepository) + mockMailTrapAPI := new(MockMailTrapAPI) + mockUserGCSAPI := new(MockUserGCSAPI) + userUseCase := NewUserUseCase(mockUserRepository, mockMailTrapAPI, mockUserGCSAPI) + + err := userUseCase.UpdatePassword(1, "") + assert.Error(t, constants.ErrAllFieldsMustBeFilled, err) + + mockUserRepository.AssertExpectations(t) + }) + + t.Run("failed password must be at least 8 characters", func(t *testing.T) { + mockUserRepository := new(MockUserRepository) + mockMailTrapAPI := new(MockMailTrapAPI) + mockUserGCSAPI := new(MockUserGCSAPI) + userUseCase := NewUserUseCase(mockUserRepository, mockMailTrapAPI, mockUserGCSAPI) + + err := userUseCase.UpdatePassword(1, "pass") + assert.Error(t, constants.ErrPasswordMustBeAtLeast8Characters, err) + + mockUserRepository.AssertExpectations(t) + }) +} + +func TestSendOTP(t *testing.T) { + t.Run("success register", func(t *testing.T) { + mockUserRepository := new(MockUserRepository) + mockMailTrapAPI := new(MockMailTrapAPI) + mockUserGCSAPI := new(MockUserGCSAPI) + userUseCase := NewUserUseCase(mockUserRepository, mockMailTrapAPI, mockUserGCSAPI) + + mockUserRepository.On("SendOTP", "user@gmail.com", mock.Anything).Return(nil) + mockMailTrapAPI.On("SendOTP", "user@gmail.com", mock.Anything, "register").Return(nil) + + err := userUseCase.SendOTP("user@gmail.com", "register") + assert.NoError(t, err) + + mockUserRepository.AssertExpectations(t) + }) + + t.Run("success forgot password", func(t *testing.T) { + mockUserRepository := new(MockUserRepository) + mockMailTrapAPI := new(MockMailTrapAPI) + mockUserGCSAPI := new(MockUserGCSAPI) + userUseCase := NewUserUseCase(mockUserRepository, mockMailTrapAPI, mockUserGCSAPI) + + mockUserRepository.On("SendOTP", "user@gmail.com", mock.Anything).Return(nil) + mockMailTrapAPI.On("SendOTP", "user@gmail.com", mock.Anything, "forgot_password").Return(nil) + + err := userUseCase.SendOTP("user@gmail.com", "forgot_password") + assert.NoError(t, err) + + mockUserRepository.AssertExpectations(t) + }) + + t.Run("failed empty field", func(t *testing.T) { + mockUserRepository := new(MockUserRepository) + mockMailTrapAPI := new(MockMailTrapAPI) + mockUserGCSAPI := new(MockUserGCSAPI) + userUseCase := NewUserUseCase(mockUserRepository, mockMailTrapAPI, mockUserGCSAPI) + + err := userUseCase.SendOTP("", "register") + assert.Error(t, constants.ErrAllFieldsMustBeFilled, err) + + mockUserRepository.AssertExpectations(t) + }) + + t.Run("failed internal server error", func(t *testing.T) { + mockUserRepository := new(MockUserRepository) + mockMailTrapAPI := new(MockMailTrapAPI) + mockUserGCSAPI := new(MockUserGCSAPI) + userUseCase := NewUserUseCase(mockUserRepository, mockMailTrapAPI, mockUserGCSAPI) + + mockUserRepository.On("SendOTP", "user@gmail.com", mock.Anything).Return(constants.ErrInternalServerError) + + err := userUseCase.SendOTP("user@gmail.com", "register") + assert.Error(t, constants.ErrInternalServerError, err) + + mockUserRepository.AssertExpectations(t) + }) + + t.Run("failed email not registered", func(t *testing.T) { + mockUserRepository := new(MockUserRepository) + mockMailTrapAPI := new(MockMailTrapAPI) + mockUserGCSAPI := new(MockUserGCSAPI) + userUseCase := NewUserUseCase(mockUserRepository, mockMailTrapAPI, mockUserGCSAPI) + + mockUserRepository.On("SendOTP", "user@gmail.com", mock.Anything).Return(constants.ErrEmailNotRegistered) + + err := userUseCase.SendOTP("user@gmail.com", "register") + assert.Error(t, constants.ErrEmailNotRegistered, err) + + mockUserRepository.AssertExpectations(t) + }) + + t.Run("failed mailtrap api error", func(t *testing.T) { + mockUserRepository := new(MockUserRepository) + mockMailTrapAPI := new(MockMailTrapAPI) + mockUserGCSAPI := new(MockUserGCSAPI) + userUseCase := NewUserUseCase(mockUserRepository, mockMailTrapAPI, mockUserGCSAPI) + + mockUserRepository.On("SendOTP", "user@gmail.com", mock.Anything).Return(nil) + mockMailTrapAPI.On("SendOTP", "user@gmail.com", mock.Anything, "register").Return(errors.New("Mailtrap API error")) + + err := userUseCase.SendOTP("user@gmail.com", "register") + assert.Error(t, constants.ErrInternalServerError, err) + + mockUserRepository.AssertExpectations(t) + }) +} + +func TestVerifyOTP(t *testing.T) { + t.Run("success register", func(t *testing.T) { + mockUserRepository := new(MockUserRepository) + mockMailTrapAPI := new(MockMailTrapAPI) + mockUserGCSAPI := new(MockUserGCSAPI) + userUseCase := NewUserUseCase(mockUserRepository, mockMailTrapAPI, mockUserGCSAPI) + + mockUserRepository.On("VerifyOTPRegister", "user@gmail.com", "12345").Return(nil) + + err := userUseCase.VerifyOTP("user@gmail.com", "12345", "register") + assert.NoError(t, err) + + mockUserRepository.AssertExpectations(t) + }) + + t.Run("success forgot password", func(t *testing.T) { + mockUserRepository := new(MockUserRepository) + mockMailTrapAPI := new(MockMailTrapAPI) + mockUserGCSAPI := new(MockUserGCSAPI) + userUseCase := NewUserUseCase(mockUserRepository, mockMailTrapAPI, mockUserGCSAPI) + mockUserRepository.On("VerifyOTPForgotPassword", "user@gmail.com", "12345").Return(nil) + + err := userUseCase.VerifyOTP("user@gmail.com", "12345", "forgot_password") + assert.NoError(t, err) + + mockUserRepository.AssertExpectations(t) + }) + + t.Run("failed empty field", func(t *testing.T) { + mockUserRepository := new(MockUserRepository) + mockMailTrapAPI := new(MockMailTrapAPI) + mockUserGCSAPI := new(MockUserGCSAPI) + userUseCase := NewUserUseCase(mockUserRepository, mockMailTrapAPI, mockUserGCSAPI) + + err := userUseCase.VerifyOTP("", "12345", "register") + assert.Error(t, constants.ErrAllFieldsMustBeFilled, err) + + mockUserRepository.AssertExpectations(t) + }) + + t.Run("failed register email not registered", func(t *testing.T) { + mockUserRepository := new(MockUserRepository) + mockMailTrapAPI := new(MockMailTrapAPI) + mockUserGCSAPI := new(MockUserGCSAPI) + userUseCase := NewUserUseCase(mockUserRepository, mockMailTrapAPI, mockUserGCSAPI) + + mockUserRepository.On("VerifyOTPRegister", "user@gmail.com", "12345").Return(constants.ErrEmailNotRegistered) + + err := userUseCase.VerifyOTP("user@gmail.com", "12345", "register") + assert.Error(t, constants.ErrEmailNotRegistered, err) + + mockUserRepository.AssertExpectations(t) + }) + + t.Run("failed forgot password email not registered", func(t *testing.T) { + mockUserRepository := new(MockUserRepository) + mockMailTrapAPI := new(MockMailTrapAPI) + mockUserGCSAPI := new(MockUserGCSAPI) + userUseCase := NewUserUseCase(mockUserRepository, mockMailTrapAPI, mockUserGCSAPI) + + mockUserRepository.On("VerifyOTPForgotPassword", "user@gmail.com", "12345").Return(constants.ErrEmailNotRegistered) + + err := userUseCase.VerifyOTP("user@gmail.com", "12345", "forgot_password") + assert.Error(t, constants.ErrEmailNotRegistered, err) + + mockUserRepository.AssertExpectations(t) + }) + + t.Run("failed register invalid otp", func(t *testing.T) { + mockUserRepository := new(MockUserRepository) + mockMailTrapAPI := new(MockMailTrapAPI) + mockUserGCSAPI := new(MockUserGCSAPI) + userUseCase := NewUserUseCase(mockUserRepository, mockMailTrapAPI, mockUserGCSAPI) + + mockUserRepository.On("VerifyOTPRegister", "user@gmail.com", "12345").Return(constants.ErrInvalidOTP) + + err := userUseCase.VerifyOTP("user@gmail.com", "12345", "register") + assert.Error(t, constants.ErrInvalidOTP, err) + + mockUserRepository.AssertExpectations(t) + }) + + t.Run("failed forgot password invalid otp", func(t *testing.T) { + mockUserRepository := new(MockUserRepository) + mockMailTrapAPI := new(MockMailTrapAPI) + mockUserGCSAPI := new(MockUserGCSAPI) + userUseCase := NewUserUseCase(mockUserRepository, mockMailTrapAPI, mockUserGCSAPI) + + mockUserRepository.On("VerifyOTPForgotPassword", "user@gmail.com", "12345").Return(constants.ErrInvalidOTP) + + err := userUseCase.VerifyOTP("user@gmail.com", "12345", "forgot_password") + assert.Error(t, constants.ErrInvalidOTP, err) + + mockUserRepository.AssertExpectations(t) + }) + + t.Run("failed internal server error register", func(t *testing.T) { + mockUserRepository := new(MockUserRepository) + mockMailTrapAPI := new(MockMailTrapAPI) + mockUserGCSAPI := new(MockUserGCSAPI) + userUseCase := NewUserUseCase(mockUserRepository, mockMailTrapAPI, mockUserGCSAPI) + + mockUserRepository.On("VerifyOTPRegister", "user@gmail.com", "12345").Return(constants.ErrInternalServerError) + + err := userUseCase.VerifyOTP("user@gmail.com", "12345", "register") + assert.Error(t, constants.ErrInternalServerError, err) + + mockUserRepository.AssertExpectations(t) + }) + + t.Run("failed internal server error forgot password", func(t *testing.T) { + mockUserRepository := new(MockUserRepository) + mockMailTrapAPI := new(MockMailTrapAPI) + mockUserGCSAPI := new(MockUserGCSAPI) + userUseCase := NewUserUseCase(mockUserRepository, mockMailTrapAPI, mockUserGCSAPI) + mockUserRepository.On("VerifyOTPForgotPassword", "user@gmail.com", "12345").Return(constants.ErrInternalServerError) + + err := userUseCase.VerifyOTP("user@gmail.com", "12345", "forgot_password") + assert.Error(t, constants.ErrInternalServerError, err) + + mockUserRepository.AssertExpectations(t) + }) + +} + +func TestUpdatePasswordForgot(t *testing.T) { + t.Run("success", func(t *testing.T) { + mockUserRepository := new(MockUserRepository) + mockMailTrapAPI := new(MockMailTrapAPI) + mockUserGCSAPI := new(MockUserGCSAPI) + userUseCase := NewUserUseCase(mockUserRepository, mockMailTrapAPI, mockUserGCSAPI) + + mockUserRepository.On("UpdatePasswordForgot", "user@gmail.com", mock.Anything).Return(nil) + + err := userUseCase.UpdatePasswordForgot("user@gmail.com", "password") + assert.NoError(t, err) + + mockUserRepository.AssertExpectations(t) + }) + + t.Run("failed empty field", func(t *testing.T) { + mockUserRepository := new(MockUserRepository) + mockMailTrapAPI := new(MockMailTrapAPI) + mockUserGCSAPI := new(MockUserGCSAPI) + userUseCase := NewUserUseCase(mockUserRepository, mockMailTrapAPI, mockUserGCSAPI) + + err := userUseCase.UpdatePasswordForgot("", "password") + assert.Error(t, constants.ErrAllFieldsMustBeFilled, err) + + mockUserRepository.AssertExpectations(t) + }) + + t.Run("failed user not found", func(t *testing.T) { + mockUserRepository := new(MockUserRepository) + mockMailTrapAPI := new(MockMailTrapAPI) + mockUserGCSAPI := new(MockUserGCSAPI) + userUseCase := NewUserUseCase(mockUserRepository, mockMailTrapAPI, mockUserGCSAPI) + + mockUserRepository.On("UpdatePasswordForgot", "user@gmail.com", mock.Anything).Return(constants.ErrUserNotFound) + + err := userUseCase.UpdatePasswordForgot("user@gmail.com", "password") + assert.Error(t, constants.ErrUserNotFound, err) + + mockUserRepository.AssertExpectations(t) + }) + + t.Run("failed internal server error", func(t *testing.T) { + mockUserRepository := new(MockUserRepository) + mockMailTrapAPI := new(MockMailTrapAPI) + mockUserGCSAPI := new(MockUserGCSAPI) + userUseCase := NewUserUseCase(mockUserRepository, mockMailTrapAPI, mockUserGCSAPI) + + mockUserRepository.On("UpdatePasswordForgot", "user@gmail.com", mock.Anything).Return(constants.ErrInternalServerError) + + err := userUseCase.UpdatePasswordForgot("user@gmail.com", "password") + assert.Error(t, constants.ErrInternalServerError, err) + + mockUserRepository.AssertExpectations(t) + }) + + t.Run("failed otp not verified", func(t *testing.T) { + mockUserRepository := new(MockUserRepository) + mockMailTrapAPI := new(MockMailTrapAPI) + mockUserGCSAPI := new(MockUserGCSAPI) + userUseCase := NewUserUseCase(mockUserRepository, mockMailTrapAPI, mockUserGCSAPI) + + mockUserRepository.On("UpdatePasswordForgot", "user@gmail.com", mock.Anything).Return(constants.ErrForgotPasswordOTPNotVerified) + + err := userUseCase.UpdatePasswordForgot("user@gmail.com", "password") + assert.Error(t, constants.ErrForgotPasswordOTPNotVerified, err) + + mockUserRepository.AssertExpectations(t) + }) + + t.Run("failed password must be at least 8 characters", func(t *testing.T) { + mockUserRepository := new(MockUserRepository) + mockMailTrapAPI := new(MockMailTrapAPI) + mockUserGCSAPI := new(MockUserGCSAPI) + userUseCase := NewUserUseCase(mockUserRepository, mockMailTrapAPI, mockUserGCSAPI) + + err := userUseCase.UpdatePasswordForgot("user@gmail.com", "pass") + assert.Error(t, constants.ErrPasswordMustBeAtLeast8Characters, err) + + mockUserRepository.AssertExpectations(t) + }) +} diff --git a/utils/convert_response_code.go b/utils/convert_response_code.go index 9b8f052..41580ab 100644 --- a/utils/convert_response_code.go +++ b/utils/convert_response_code.go @@ -36,6 +36,12 @@ func ConvertResponseCode(err error) int { constants.ErrLimitMustBeFilled, constants.ErrInvalidFileFormat, constants.ErrForgotPasswordOTPNotVerified, + constants.ErrConfirmPasswordDoesntMatch, + constants.ErrColumnsDoesntMatch, + constants.ErrInvalidCategoryIDFormat, + constants.ErrEmailNotRegistered, + constants.ErrPasswordMustBeAtLeast8Characters, + constants.ErrCategoryHasBeenUsed, } var notFoundErrors = []error{ diff --git a/utils/get_rows_from_excel.go b/utils/get_rows_from_excel.go new file mode 100644 index 0000000..fd52249 --- /dev/null +++ b/utils/get_rows_from_excel.go @@ -0,0 +1,42 @@ +package utils + +import ( + "e-complaint-api/constants" + "io" + "mime/multipart" + "os" + + "github.com/xuri/excelize/v2" +) + +func GetRowsFromExcel(file *multipart.FileHeader) ([][]string, error) { + f, err := file.Open() + if err != nil { + return nil, constants.ErrInternalServerError + } + defer f.Close() + + tempFile, err := os.CreateTemp("", "uploaded-*.xlsx") + if err != nil { + return nil, constants.ErrInternalServerError + } + defer os.Remove(tempFile.Name()) + defer tempFile.Close() + + if _, err := io.Copy(tempFile, f); err != nil { + return nil, constants.ErrInternalServerError + } + + excelFile, err := excelize.OpenFile(tempFile.Name()) + if err != nil { + return nil, constants.ErrInternalServerError + } + defer excelFile.Close() + + rows, err := excelFile.GetRows("Sheet1") + if err != nil { + return nil, constants.ErrInternalServerError + } + + return rows, nil +}