feat: add /setcalendar command, store tests, and improve error handling
All checks were successful
CI / build (push) Successful in 48s

- Add /setcalendar command for calendar type parity (Settings UI)
- Add 17 store tests covering all repository methods
- Add handler tests for formatDuration, fmtHHMM, fmtDDHHMM
- Add domain tests for BurnoutLevel.String, NavMonth, FormatSalary
- Fix silent error discarding in save* helpers (log write failures)
- Fix computeAndAwardXP error handling instead of discarding
- Wrap critical store.go SQL errors with fmt.Errorf context
- Update README commands table with /setcalendar
This commit is contained in:
2026-06-25 15:56:00 +03:30
parent d9e13cb51d
commit 7dbd9fda12
8 changed files with 816 additions and 22 deletions

View File

@@ -600,6 +600,48 @@ func (h *Handler) processTimezoneInput(ctx context.Context, msg *models.Message,
fmt.Sprintf("Timezone set to %s (current time: %s)", text, now.Format(domain.TimeLayout)), &backKB)
}
func (h *Handler) handleSetCalendar(ctx context.Context, msg *models.Message, user *domain.User) {
args := strings.TrimSpace(strings.TrimPrefix(msg.Text, "/setcalendar"))
if args == "" {
h.promptForInput(ctx, msg.Chat.ID, 0, user.ID, PendingCalendar,
fmt.Sprintf("Current calendar: %s\n\nValid options: gregorian, jalali, hijri", user.Calendar), nil)
return
}
args = strings.ToLower(args)
valid := map[string]bool{"gregorian": true, "jalali": true, "hijri": true}
if !valid[args] {
h.sendText(ctx, msg.Chat.ID, "Invalid calendar. Valid options: gregorian, jalali, hijri")
return
}
h.saveCalendar(ctx, msg.Chat.ID, user, args)
}
func (h *Handler) processCalendarInput(ctx context.Context, msg *models.Message, user *domain.User, pi *PendingInput, text string) {
text = strings.ToLower(strings.TrimSpace(text))
valid := map[string]bool{"gregorian": true, "jalali": true, "hijri": true}
if !valid[text] {
h.editText(ctx, msg.Chat.ID, pi.MsgID,
"Invalid calendar. Valid options: gregorian, jalali, hijri", &models.InlineKeyboardMarkup{
InlineKeyboard: [][]models.InlineKeyboardButton{
{{Text: "Try again", CallbackData: "caltype"}},
{{Text: "Cancel", CallbackData: "back_settings"}},
},
})
return
}
h.saveCalendar(ctx, msg.Chat.ID, user, text)
backKB := settingsBackKeyboard()
h.editText(ctx, msg.Chat.ID, pi.MsgID,
fmt.Sprintf("Calendar set to %s", text), &backKB)
}
func (h *Handler) saveCalendar(_ context.Context, _ int64, user *domain.User, cal string) {
user.Calendar = cal
if err := h.Repo.UpdateUser(user); err != nil {
slog.Error("save calendar failed", "user_id", user.ID, "error", err)
}
}
func (h *Handler) processBreakInput(ctx context.Context, msg *models.Message, user *domain.User, pi *PendingInput, text string) {
mins, err := strconv.Atoi(text)
if err != nil || mins < 0 || mins > domain.MaxBreakThresholdMins {
@@ -684,15 +726,20 @@ func (h *Handler) saveRate(ctx context.Context, chatID int64, user *domain.User,
func (h *Handler) saveCurrency(_ context.Context, _ int64, user *domain.User, currency string) {
st, err := h.Repo.GetOrCreateSettings(user.ID)
if err != nil {
slog.Error("save currency: get settings failed", "user_id", user.ID, "error", err)
return
}
st.Currency = currency
h.Repo.UpdateSettings(st)
if err := h.Repo.UpdateSettings(st); err != nil {
slog.Error("save currency: update settings failed", "user_id", user.ID, "error", err)
}
}
func (h *Handler) saveTimezone(_ context.Context, _ int64, user *domain.User, tz string) {
user.Timezone = tz
h.Repo.UpdateUser(user)
if err := h.Repo.UpdateUser(user); err != nil {
slog.Error("save timezone failed", "user_id", user.ID, "error", err)
}
}
func (h *Handler) saveBreakThreshold(_ context.Context, _ int64, user *domain.User, mins int) {
@@ -700,10 +747,13 @@ func (h *Handler) saveBreakThreshold(_ context.Context, _ int64, user *domain.Us
today := time.Now().In(loc).Format(domain.DateLayout)
day, err := h.Repo.GetOrCreateDay(user.ID, today)
if err != nil {
slog.Error("save break threshold: get day failed", "user_id", user.ID, "error", err)
return
}
day.MinBreakThreshold = int64(mins) * 60
h.Repo.UpdateDay(day)
if err := h.Repo.UpdateDay(day); err != nil {
slog.Error("save break threshold: update day failed", "user_id", user.ID, "error", err)
}
}
func (h *Handler) saveUsername(_ context.Context, _ int64, user *domain.User, name string) {
@@ -712,7 +762,9 @@ func (h *Handler) saveUsername(_ context.Context, _ int64, user *domain.User, na
return
}
user.Username = name
h.Repo.UpdateUser(user)
if err := h.Repo.UpdateUser(user); err != nil {
slog.Error("save username failed", "user_id", user.ID, "error", err)
}
}
func (h *Handler) validateAndSaveReportTime(_ context.Context, _ int64, user *domain.User, timeStr string) error {