diff --git a/controller/questionnaire.go b/controller/questionnaire.go index 34c90ea8..f4707b82 100644 --- a/controller/questionnaire.go +++ b/controller/questionnaire.go @@ -879,49 +879,23 @@ func (q *Questionnaire) DeleteQuestionnaire(c echo.Context, questionnaireID int) return nil } -func (q *Questionnaire) GetQuestionnaireMyRemindStatus(c echo.Context, questionnaireID int) (bool, error) { - status, err := q.CheckRemindStatus(questionnaireID) +func (q *Questionnaire) GetQuestionnaireMyRemindStatus(c echo.Context, questionnaireID int, userID string) (bool, error) { + status, err := q.GetTargetsCancelStatus(c.Request().Context(), questionnaireID, []string{userID}) if err != nil { c.Logger().Errorf("failed to check remind status: %+v", err) return false, echo.NewHTTPError(http.StatusInternalServerError, "failed to check remind status") } - return status, nil + return !status[0].IsCanceled, nil } -func (q *Questionnaire) EditQuestionnaireMyRemindStatus(c echo.Context, questionnaireID int, isRemindEnabled bool) error { - if isRemindEnabled { - status, err := q.CheckRemindStatus(questionnaireID) - if err != nil { - c.Logger().Errorf("failed to check remind status: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, "failed to check remind status") - } - if status { - return nil - } - - questionnaire, _, _, _, _, _, _, _, err := q.GetQuestionnaireInfo(c.Request().Context(), questionnaireID) - if err != nil { - if errors.Is(err, model.ErrRecordNotFound) { - c.Logger().Info("questionnaire not found") - return echo.NewHTTPError(http.StatusNotFound, "questionnaire not found") - } - c.Logger().Errorf("failed to get questionnaire: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, "failed to get questionnaire") - } - - err = q.PushReminder(questionnaireID, &questionnaire.ResTimeLimit.Time) - if err != nil { - c.Logger().Errorf("failed to push reminder: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, "failed to push reminder") - } - } else { - err := q.DeleteReminder(questionnaireID) - if err != nil { - c.Logger().Errorf("failed to delete reminder: %+v", err) - return echo.NewHTTPError(http.StatusInternalServerError, "failed to delete reminder") - } +func (q *Questionnaire) EditQuestionnaireMyRemindStatus(c echo.Context, questionnaireID int, userID string, isRemindEnabled bool) error { + err := q.UpdateTargetsCancelStatus(c.Request().Context(), questionnaireID, []string{userID}, !isRemindEnabled) + if err != nil { + c.Logger().Errorf("failed to update remind status: %+v", err) + return echo.NewHTTPError(http.StatusInternalServerError, "failed to update remind status") } + return nil } diff --git a/handler/questionnaire.go b/handler/questionnaire.go index e0194de0..5a8e42b6 100644 --- a/handler/questionnaire.go +++ b/handler/questionnaire.go @@ -98,8 +98,14 @@ func (h Handler) DeleteQuestionnaire(ctx echo.Context, questionnaireID openapi.Q // (GET /questionnaires/{questionnaireID}/myRemindStatus) func (h Handler) GetQuestionnaireMyRemindStatus(ctx echo.Context, questionnaireID openapi.QuestionnaireIDInPath) error { + userID, err := h.Middleware.GetUserID(ctx) + if err != nil { + ctx.Logger().Errorf("failed to get userID: %+v", err) + return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get userID: %w", err)) + } + res := openapi.QuestionnaireIsRemindEnabled{} - status, err := h.Questionnaire.GetQuestionnaireMyRemindStatus(ctx, questionnaireID) + status, err := h.Questionnaire.GetQuestionnaireMyRemindStatus(ctx, questionnaireID, userID) if err != nil { ctx.Logger().Errorf("failed to get questionnaire my remind status: %+v", err) return err @@ -111,13 +117,19 @@ func (h Handler) GetQuestionnaireMyRemindStatus(ctx echo.Context, questionnaireI // (PATCH /questionnaires/{questionnaireID}/myRemindStatus) func (h Handler) EditQuestionnaireMyRemindStatus(ctx echo.Context, questionnaireID openapi.QuestionnaireIDInPath) error { + userID, err := h.Middleware.GetUserID(ctx) + if err != nil { + ctx.Logger().Errorf("failed to get userID: %+v", err) + return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to get userID: %w", err)) + } + params := openapi.EditQuestionnaireMyRemindStatusJSONRequestBody{} if err := ctx.Bind(¶ms); err != nil { ctx.Logger().Errorf("failed to bind request body: %+v", err) return echo.NewHTTPError(http.StatusBadRequest, fmt.Errorf("failed to bind request body: %w", err)) } - err := h.Questionnaire.EditQuestionnaireMyRemindStatus(ctx, questionnaireID, params.IsRemindEnabled) + err = h.Questionnaire.EditQuestionnaireMyRemindStatus(ctx, questionnaireID, userID, params.IsRemindEnabled) if err != nil { ctx.Logger().Errorf("failed to edit questionnaire my remind status: %+v", err) return err diff --git a/model/targets.go b/model/targets.go index e6d4cc17..75e86403 100644 --- a/model/targets.go +++ b/model/targets.go @@ -10,5 +10,6 @@ type ITarget interface { DeleteTargets(ctx context.Context, questionnaireID int) error GetTargets(ctx context.Context, questionnaireIDs []int) ([]Targets, error) IsTargetingMe(ctx context.Context, quesionnairID int, userID string) (bool, error) - CancelTargets(ctx context.Context, questionnaireID int, targets []string) error + GetTargetsCancelStatus(ctx context.Context, questionnaireID int, targets []string) ([]Targets, error) + UpdateTargetsCancelStatus(ctx context.Context, questionnaireID int, targets []string, cancelStatus bool) error } diff --git a/model/targets_impl.go b/model/targets_impl.go index 929a7df9..b6131a66 100644 --- a/model/targets_impl.go +++ b/model/targets_impl.go @@ -104,8 +104,27 @@ func (*Target) IsTargetingMe(ctx context.Context, questionnairID int, userID str return false, nil } -// CancelTargets アンケートの対象をキャンセル(削除しない) -func (*Target) CancelTargets(ctx context.Context, questionnaireID int, targets []string) error { +func (*Target) GetTargetsCancelStatus(ctx context.Context, questionnaireID int, targets []string) ([]Targets, error) { + db, err := getTx(ctx) + if err != nil { + return nil, fmt.Errorf("failed to get transaction: %w", err) + } + + var cancelStatus []Targets + err = db. + Where("questionnaire_id = ? AND user_traqid IN (?)", questionnaireID, targets). + Find(&cancelStatus).Error + if err != nil { + return nil, fmt.Errorf("failed to get targets remind status: %w", err) + } + if len(cancelStatus) != len(targets) { + return nil, fmt.Errorf("not all targets found") + } + + return cancelStatus, nil +} + +func (*Target) UpdateTargetsCancelStatus(ctx context.Context, questionnaireID int, targets []string, cancelStatus bool) error { db, err := getTx(ctx) if err != nil { return fmt.Errorf("failed to get transaction: %w", err) @@ -114,7 +133,7 @@ func (*Target) CancelTargets(ctx context.Context, questionnaireID int, targets [ err = db. Model(&Targets{}). Where("questionnaire_id = ? AND user_traqid IN (?)", questionnaireID, targets). - Update("is_canceled", true).Error + Update("is_canceled", cancelStatus).Error if err != nil { return fmt.Errorf("failed to cancel targets: %w", err) } diff --git a/model/targets_test.go b/model/targets_test.go index e320c541..9ce0fea9 100644 --- a/model/targets_test.go +++ b/model/targets_test.go @@ -3,6 +3,7 @@ package model import ( "context" "errors" + "sort" "testing" "time" @@ -377,9 +378,137 @@ func TestIsTargetingMe(t *testing.T) { } } -func TestCancelTargets(t *testing.T) { +func TestGetTargetsCancelStatus(t *testing.T) { t.Parallel() + assertion := assert.New(t) + + ctx := context.Background() + + type test struct { + description string + validTargets []string // is_canceled = false + invalidTargets []string // is_canceled = true + argTargets []string + argTargetsValid []bool + isErr bool + err error + } + + testCases := []test{ + { + description: "all valid targets", + validTargets: []string{"a", "b"}, + invalidTargets: []string{}, + argTargets: []string{"a"}, + argTargetsValid: []bool{true}, + }, + { + description: "all invalid targets", + validTargets: []string{}, + invalidTargets: []string{"a", "b"}, + argTargets: []string{"b"}, + argTargetsValid: []bool{false}, + }, + { + description: "both valid and invalid targets", + validTargets: []string{"a", "b"}, + invalidTargets: []string{"c", "d"}, + argTargets: []string{"a", "c"}, + argTargetsValid: []bool{true, false}, + }, + { + description: "both valid and invalid targets changed order", + validTargets: []string{"a", "b"}, + invalidTargets: []string{"c", "d"}, + argTargets: []string{"b", "a", "d"}, + argTargetsValid: []bool{true, true, false}, + }, + { + description: "both valid and invalid targets changed order", + validTargets: []string{"a", "b"}, + invalidTargets: []string{"c", "d"}, + argTargets: []string{"b", "d", "a"}, + argTargetsValid: []bool{true, false, true}, + }, + { + description: "argTargets with target not in db", + validTargets: []string{"a", "b"}, + invalidTargets: []string{"c", "d"}, + argTargets: []string{"e"}, + isErr: true, + }, + { + description: "argTargets with some of target not in db", + validTargets: []string{"a", "b"}, + invalidTargets: []string{"c", "d"}, + argTargets: []string{"a", "e"}, + isErr: true, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.description, func(t *testing.T) { + targets := make([]Targets, 0, len(testCase.validTargets)+len(testCase.invalidTargets)) + for _, target := range testCase.validTargets { + targets = append(targets, Targets{ + UserTraqid: target, + IsCanceled: false, + }) + } + for _, target := range testCase.invalidTargets { + targets = append(targets, Targets{ + UserTraqid: target, + IsCanceled: true, + }) + } + questionnaire := Questionnaires{ + Targets: targets, + } + err := db. + Session(&gorm.Session{}). + Create(&questionnaire).Error + if err != nil { + t.Errorf("failed to create questionnaire: %v", err) + } + + cancelStatus, err := targetImpl.GetTargetsCancelStatus(ctx, questionnaire.ID, testCase.argTargets) + if !testCase.isErr { + assertion.NoError(err, testCase.description, "no error") + } else if testCase.err != nil { + assertion.Equal(true, errors.Is(err, testCase.err), testCase.description, "errorIs") + } else { + assertion.Error(err, testCase.description, "any error") + } + if err != nil { + return + } + + var actualCancelStatus []Targets + for i := range testCase.argTargets { + actualCancelStatus = append(actualCancelStatus, Targets{ + QuestionnaireID: questionnaire.ID, + UserTraqid: testCase.argTargets[i], + IsCanceled: !testCase.argTargetsValid[i], + }) + } + + sort.Slice(cancelStatus, func(i, j int) bool { + return cancelStatus[i].UserTraqid < cancelStatus[j].UserTraqid + }) + sort.Slice(actualCancelStatus, func(i, j int) bool { + return actualCancelStatus[i].UserTraqid < actualCancelStatus[j].UserTraqid + }) + assert.Equal(t, cancelStatus, actualCancelStatus, testCase.description, "cancelStatus") + }) + } +} + +func TestUpdateTargetsCancelStatus(t *testing.T) { + t.Parallel() + + assertion := assert.New(t) + ctx := context.Background() type test struct { @@ -388,7 +517,8 @@ func TestCancelTargets(t *testing.T) { beforeInvalidTargets []string afterValidTargets []string afterInvalidTargets []string - argCancelTargets []string + argUpdateTargets []string + argCancelStatus bool isErr bool err error } @@ -400,7 +530,8 @@ func TestCancelTargets(t *testing.T) { beforeInvalidTargets: []string{}, afterValidTargets: []string{}, afterInvalidTargets: []string{"a"}, - argCancelTargets: []string{"a"}, + argUpdateTargets: []string{"a"}, + argCancelStatus: true, }, { description: "キャンセルするtargetが複数でエラーなし", @@ -408,7 +539,8 @@ func TestCancelTargets(t *testing.T) { beforeInvalidTargets: []string{}, afterValidTargets: []string{}, afterInvalidTargets: []string{"a", "b"}, - argCancelTargets: []string{"a", "b"}, + argUpdateTargets: []string{"a", "b"}, + argCancelStatus: true, }, { description: "キャンセルするtargetがないときエラーなし", @@ -416,16 +548,44 @@ func TestCancelTargets(t *testing.T) { beforeInvalidTargets: []string{}, afterValidTargets: []string{"a"}, afterInvalidTargets: []string{}, - argCancelTargets: []string{}, + argUpdateTargets: []string{}, + argCancelStatus: true, }, { - description: "キャンセルするtargetが見つからないときエラー", + description: "キャンセルするtargetが見つからないときエラーなし", beforeValidTargets: []string{"a"}, beforeInvalidTargets: []string{}, afterValidTargets: []string{"a"}, afterInvalidTargets: []string{}, - argCancelTargets: []string{"b"}, - isErr: true, + argUpdateTargets: []string{"b"}, + argCancelStatus: true, + }, + { + description: "再開するtargetが1人でエラーなし", + beforeValidTargets: []string{"a"}, + beforeInvalidTargets: []string{"b"}, + afterValidTargets: []string{"a", "b"}, + afterInvalidTargets: []string{}, + argUpdateTargets: []string{"b"}, + argCancelStatus: false, + }, + { + description: "再開するtargetが複数でエラーなし", + beforeValidTargets: []string{}, + beforeInvalidTargets: []string{"a", "b"}, + afterValidTargets: []string{"a", "b"}, + afterInvalidTargets: []string{}, + argUpdateTargets: []string{"a", "b"}, + argCancelStatus: false, + }, + { + description: "再開するtargetがないときエラーなし", + beforeValidTargets: []string{"a"}, + beforeInvalidTargets: []string{}, + afterValidTargets: []string{"a"}, + afterInvalidTargets: []string{}, + argUpdateTargets: []string{}, + argCancelStatus: false, }, } @@ -454,13 +614,15 @@ func TestCancelTargets(t *testing.T) { t.Errorf("failed to create questionnaire: %v", err) } - err = targetImpl.CancelTargets(ctx, questionnaire.ID, testCase.argCancelTargets) + err = targetImpl.UpdateTargetsCancelStatus(ctx, questionnaire.ID, testCase.argUpdateTargets, testCase.argCancelStatus) + if !testCase.isErr { + assertion.NoError(err, testCase.description, "no error") + } else if testCase.err != nil { + assertion.Equal(true, errors.Is(err, testCase.err), testCase.description, "errorIs") + } else { + assertion.Error(err, testCase.description, "any error") + } if err != nil { - if !testCase.isErr { - t.Errorf("unexpected error: %v", err) - } else if !errors.Is(err, testCase.err) { - t.Errorf("invalid error: expected: %+v, actual: %+v", testCase.err, err) - } return }