diff --git a/internal/bot/bot.go b/internal/bot/bot.go index cb6a8bd..720cda6 100644 --- a/internal/bot/bot.go +++ b/internal/bot/bot.go @@ -16,15 +16,16 @@ type Bot struct { b *tele.Bot // Кнопки - editBtn *tele.Btn - chooseBtn *tele.Btn - returnBtn *tele.Btn - refreshBtn *tele.Btn - pushBtn *tele.Btn - letAheadBtn *tele.Btn - popBtn *tele.Btn - clearBtn *tele.Btn - removeBtn *tele.Btn + editBtn *tele.Btn + chooseBtn *tele.Btn + returnBtn *tele.Btn + refreshBtn *tele.Btn + pushBtn *tele.Btn + pushPriorityBtn *tele.Btn + letAheadBtn *tele.Btn + popBtn *tele.Btn + clearBtn *tele.Btn + removeBtn *tele.Btn // Менюшки startMenu *tele.ReplyMarkup @@ -65,6 +66,7 @@ func New( returnBtn := markup.Data("Назад", "return") refreshBtn := markup.Data("Обновить", "update") pushBtn := markup.Data("Записаться", "push") + pushPriorityBtn := markup.Data("Записаться на место", "push-priority") popBtn := markup.Data("Позвать на сдачу", "pop") clearBtn := markup.Data("Очистить очередь", "clear") letAheadBtn := markup.Data("Пропустить в очереди", "let-ahead") @@ -81,6 +83,7 @@ func New( subjectMenu.Inline( markup.Row(returnBtn, refreshBtn), markup.Row(pushBtn), + markup.Row(pushPriorityBtn), markup.Row(letAheadBtn), markup.Row(removeBtn), ) @@ -90,6 +93,7 @@ func New( subjectAdminMenu.Inline( markup.Row(returnBtn, refreshBtn), markup.Row(pushBtn), + markup.Row(pushPriorityBtn), markup.Row(letAheadBtn), markup.Row(removeBtn), markup.Row(popBtn), @@ -99,15 +103,16 @@ func New( return &Bot{ b: b, - editBtn: &editBtn, - chooseBtn: &chooseBtn, - returnBtn: &returnBtn, - refreshBtn: &refreshBtn, - pushBtn: &pushBtn, - letAheadBtn: &letAheadBtn, - popBtn: &popBtn, - clearBtn: &clearBtn, - removeBtn: &removeBtn, + editBtn: &editBtn, + chooseBtn: &chooseBtn, + returnBtn: &returnBtn, + refreshBtn: &refreshBtn, + pushBtn: &pushBtn, + pushPriorityBtn: &pushPriorityBtn, + letAheadBtn: &letAheadBtn, + popBtn: &popBtn, + clearBtn: &clearBtn, + removeBtn: &removeBtn, startMenu: startMenu, subjectMenu: subjectMenu, @@ -162,6 +167,7 @@ func (b *Bot) Register( // Требует получить очередь из кеша authorized.Handle(b.refreshBtn, handlers.Refresh, middlewares.GetQueue) authorized.Handle(b.pushBtn, handlers.Push, middlewares.GetQueue) + authorized.Handle(b.pushPriorityBtn, handlers.PushPriority, middlewares.GetQueue) authorized.Handle(b.letAheadBtn, handlers.LetAhead, middlewares.GetQueue) authorized.Handle(b.removeBtn, handlers.Remove, middlewares.GetQueue) authorized.Handle(b.popBtn, handlers.Pop, middlewares.GetQueue) diff --git a/internal/handlers/bot/queue.go b/internal/handlers/bot/queue.go index ea96bbd..61e8e38 100644 --- a/internal/handlers/bot/queue.go +++ b/internal/handlers/bot/queue.go @@ -40,6 +40,58 @@ func (b *Bot) Push(c telebot.Context) error { return b.showSubject(c, queue, entry) } +// Пушает в очередь с указанием на конкретное место +func (b *Bot) PushPriority(c telebot.Context) error { + queue := c.Get("queue").(models.Queue) + + entry := models.QueueEntry{ + ChatID: fmt.Sprint(c.Chat().ID), + } + + // Пробуем пока не получится встать в очередь +getPos: + if err := c.Send("Введите на какую позицию в очереди хотите встать"); err != nil { + return nil + } + + // Получаем от пользователя ввод числа + if err := b.dialogue(c, func(ch <-chan string, c telebot.Context) error { + for pos := range ch { + if posInt, err := strconv.Atoi(pos); err == nil && posInt > 0 { + entry.Position = posInt + break + } + + if err := c.Send("Невозможно привести к числу или неверное число, попробуйте снова"); err != nil { + return nil + } + } + return nil + }); err != nil { + return err + } + + err := b.queueService.Push(b.ctx, queue, entry) + if err != nil { + if errors.Is(err, services.ErrAlreadyInQueue) { + return c.Send("Вы уже в очереди") + } + if errors.Is(err, services.ErrPlaceTaken) { + // Если место занято продолжаем цикл, пока пользователь не введёт + // доступную позицию в очереди + err = c.Send("Место уже занято") + if err != nil { + return err + } + goto getPos + } + + return err + } + + return b.showSubject(c, queue, entry) +} + // Попает из очереди func (b *Bot) Pop(c telebot.Context) error { queue := c.Get("queue").(models.Queue) @@ -97,7 +149,6 @@ func (b *Bot) Pop(c telebot.Context) error { // Пропускает следующего в очереди func (b *Bot) LetAhead(c telebot.Context) error { queue := c.Get("queue").(models.Queue) - entry := models.QueueEntry{ ChatID: fmt.Sprint(c.Chat().ID), } @@ -107,9 +158,6 @@ func (b *Bot) LetAhead(c telebot.Context) error { if errors.Is(err, services.ErrNotFound) { return c.Send("Вы не записаны в очередь") } - if errors.Is(err, services.ErrQueueEnd) { - return c.Send("Вы последний в очереди") - } return err } @@ -254,7 +302,7 @@ func (b *Bot) showSubject( sb.WriteString("\nОчередь пуста") } else if err == nil { // Находим имена пользователей - for i, entry := range entries { + for _, entry := range entries { chatID, err := strconv.ParseInt(entry.ChatID, 10, 64) if err != nil { return err @@ -267,9 +315,9 @@ func (b *Bot) showSubject( // Если это текущий пользователь, то выделяем жирным для видимости if chatID == c.Chat().ID { - fmt.Fprintf(&sb, "\n*%3d. %s*", i+1, user.Name) + fmt.Fprintf(&sb, "\n*%3d. %s*", entry.Position, user.Name) } else { - fmt.Fprintf(&sb, "\n%3d. %s", i+1, user.Name) + fmt.Fprintf(&sb, "\n%3d. %s", entry.Position, user.Name) } } diff --git a/internal/handlers/bot/users.go b/internal/handlers/bot/users.go index d88893d..066b0e1 100644 --- a/internal/handlers/bot/users.go +++ b/internal/handlers/bot/users.go @@ -131,7 +131,7 @@ func (b *Bot) getUser(c tele.Context) (models.User, error) { } // Читаем оставшиеся данные - err = b.dialogue(c, func(ch <-chan string, c tele.Context) error { + if err = b.dialogue(c, func(ch <-chan string, c tele.Context) error { var err error menu.Reply( @@ -165,8 +165,7 @@ func (b *Bot) getUser(c tele.Context) (models.User, error) { } return nil - }) - if err != nil { + }); err != nil { return models.User{}, err } diff --git a/internal/interfaces/bot.go b/internal/interfaces/bot.go index 18771dc..a13cbb1 100644 --- a/internal/interfaces/bot.go +++ b/internal/interfaces/bot.go @@ -18,6 +18,7 @@ type BotHandlers interface { ChooseSubjectButton(telebot.Context) error Refresh(telebot.Context) error Push(telebot.Context) error + PushPriority(telebot.Context) error Pop(telebot.Context) error LetAhead(telebot.Context) error Clear(telebot.Context) error diff --git a/internal/models/queue.go b/internal/models/queue.go index 336a764..64005f3 100644 --- a/internal/models/queue.go +++ b/internal/models/queue.go @@ -3,6 +3,8 @@ package models import ( "fmt" "strings" + + "github.com/redis/go-redis/v9" ) type Queue struct { @@ -23,5 +25,13 @@ func (q *Queue) Key() string { } type QueueEntry struct { - ChatID string + Position int + ChatID string +} + +func (e *QueueEntry) ToRedis() redis.Z { + return redis.Z{ + Score: float64(e.Position), + Member: e.ChatID, + } } diff --git a/internal/repositories/errors.go b/internal/repositories/errors.go index 2353b9d..6c6e276 100644 --- a/internal/repositories/errors.go +++ b/internal/repositories/errors.go @@ -5,5 +5,5 @@ import "errors" var ( ErrNotFound = errors.New("resource not found") ErrAlreadyInQueue = errors.New("user already in queue") - ErrCacheMiss = errors.New("cache miss") + ErrPlaceTaken = errors.New("place in queue already taken") ) diff --git a/internal/repositories/redis/cache.go b/internal/repositories/redis/cache.go index 01d92c0..364bf8a 100644 --- a/internal/repositories/redis/cache.go +++ b/internal/repositories/redis/cache.go @@ -35,7 +35,7 @@ func (s *Storage) Get( val, err := s.cl.Get(ctx, key).Result() if err != nil { if errors.Is(err, redis.Nil) { - return "", fmt.Errorf("%s: %w", op, repositories.ErrCacheMiss) + return "", fmt.Errorf("%s: %w", op, repositories.ErrNotFound) } return "", fmt.Errorf("%s: %w", op, err) diff --git a/internal/repositories/redis/queue.go b/internal/repositories/redis/queue.go index 6bffe9b..ec5db69 100644 --- a/internal/repositories/redis/queue.go +++ b/internal/repositories/redis/queue.go @@ -19,14 +19,46 @@ func (s *Storage) Push( // Пытаемся найти данный айди в очереди // Если есть, значит пользователь уже есть в очереди - _, err := s.cl.LPos(ctx, queue.Key(), entry.ChatID, redis.LPosArgs{}).Result() + _, err := s.cl.ZRank(ctx, queue.Key(), entry.ChatID).Result() if err == nil { return fmt.Errorf("%s: %w", op, repositories.ErrAlreadyInQueue) } else if !errors.Is(err, redis.Nil) { return fmt.Errorf("%s: %w", op, err) } - _, err = s.cl.RPush(ctx, queue.Key(), entry.ChatID).Result() + if entry.Position == 0 { + // Ищем последнюю позицию на которую можно поставить элемент + entries, err := s.cl.ZRangeWithScores(ctx, queue.Key(), 0, -1).Result() + if err != nil { + return fmt.Errorf("%s: %w", op, err) + } + + pos := 1 + // Проходимся по списку позиций, пока не находим пустое место + for _, e := range entries { + if e.Score != float64(pos) { + break + } + pos++ + } + entry.Position = pos + } else { + // Проверяем что на данное место можно встать + entry, err := s.cl.ZRangeByScore(ctx, queue.Key(), &redis.ZRangeBy{ + Min: fmt.Sprint(entry.Position), + Max: fmt.Sprint(entry.Position), + }).Result() + if err != nil { + return fmt.Errorf("%s: %w", op, err) + } + + if len(entry) != 0 { + // На данное место уже можно встать => неверно выбрана позиция + return fmt.Errorf("%s: %w", op, repositories.ErrPlaceTaken) + } + } + + _, err = s.cl.ZAdd(ctx, queue.Key(), entry.ToRedis()).Result() if err != nil { return fmt.Errorf("%s: %w", op, err) } @@ -40,7 +72,7 @@ func (s *Storage) Pop( ) (models.QueueEntry, error) { const op = "redis.Pop" - student, err := s.cl.LPop(ctx, queue.Key()).Result() + student, err := s.cl.ZPopMin(ctx, queue.Key()).Result() if err != nil { if errors.Is(err, redis.Nil) { return models.QueueEntry{}, fmt.Errorf("%s: %w", op, repositories.ErrNotFound) @@ -48,8 +80,21 @@ func (s *Storage) Pop( return models.QueueEntry{}, fmt.Errorf("%s: %w", op, err) } + // Уменьшаем позиции в очереди на 1 + entries, err := s.cl.ZRange(ctx, queue.Key(), 0, -1).Result() + if err != nil { + return models.QueueEntry{}, fmt.Errorf("%s: %w", op, err) + } + + for _, entry := range entries { + _, err := s.cl.ZIncrBy(ctx, queue.Key(), -1, entry).Result() + if err != nil { + return models.QueueEntry{}, fmt.Errorf("%s: %w", op, err) + } + } + return models.QueueEntry{ - ChatID: student, + ChatID: student[0].Member.(string), }, nil } @@ -60,19 +105,20 @@ func (s *Storage) Range( ) ([]models.QueueEntry, error) { const op = "redis.Range" - students, err := s.cl.LRange(ctx, queue.Key(), 0, n-1).Result() + students, err := s.cl.ZRangeWithScores(ctx, queue.Key(), 0, n-1).Result() + if err != nil { + return nil, fmt.Errorf("%s: %w", op, err) + } // Очередь не создана if len(students) == 0 { return nil, fmt.Errorf("%s: %w", op, repositories.ErrNotFound) } - if err != nil { - return nil, fmt.Errorf("%s: %w", op, err) - } entries := make([]models.QueueEntry, 0, n) for _, student := range students { entries = append(entries, models.QueueEntry{ - ChatID: student, + Position: int(student.Score), + ChatID: student.Member.(string), }) } @@ -103,7 +149,7 @@ func (s *Storage) GetPosition( ) (int64, error) { const op = "redis.GetPosition" - pos, err := s.cl.LPos(ctx, queue.Key(), entry.ChatID, redis.LPosArgs{}).Result() + pos, err := s.cl.ZScore(ctx, queue.Key(), entry.ChatID).Result() if err != nil { if errors.Is(err, redis.Nil) { return 0, fmt.Errorf("%s: %w", op, repositories.ErrNotFound) @@ -111,8 +157,7 @@ func (s *Storage) GetPosition( return 0, fmt.Errorf("%s: %w", op, err) } - // Отсчёт позиции должен начинаться с 1 - return pos + 1, nil + return int64(pos), nil } func (s *Storage) Len( @@ -121,7 +166,7 @@ func (s *Storage) Len( ) (int64, error) { const op = "redis.Len" - len, err := s.cl.LLen(ctx, queue.Key()).Result() + len, err := s.cl.ZCard(ctx, queue.Key()).Result() if err != nil { return 0, fmt.Errorf("%s: %w", op, err) } @@ -136,33 +181,43 @@ func (s *Storage) LetAhead( ) error { const op = "redis.LetAhead" - pos, err := s.cl.LPos(ctx, queue.Key(), entry.ChatID, redis.LPosArgs{}).Result() + pos, err := s.cl.ZScore(ctx, queue.Key(), entry.ChatID).Result() if err != nil { - // Списка нет или элемента нет в списке + // Элемента нет в списке if errors.Is(err, redis.Nil) { return fmt.Errorf("%s: %w", op, repositories.ErrNotFound) } return fmt.Errorf("%s: %w", op, err) } - ahead, err := s.cl.LIndex(ctx, queue.Key(), pos+1).Result() + // Проверяем есть ли на позиции выше элемент очереди + aheadPos := fmt.Sprint(pos + 1) + ahead, err := s.cl.ZRangeByScore(ctx, queue.Key(), &redis.ZRangeBy{ + Min: aheadPos, + Max: aheadPos, + }).Result() if err != nil { - // Элемент не найден так как изначальный элемент в конце списка - if errors.Is(err, redis.Nil) { - return fmt.Errorf("%s: %w", op, repositories.ErrNotFound) - } return fmt.Errorf("%s: %w", op, err) } - // Свапаем элементы - _, err = s.cl.LSet(ctx, queue.Key(), pos, ahead).Result() - if err != nil { - return fmt.Errorf("%s: %w", op, err) - } + if len(ahead) == 0 { + // Элемент не найден так как изначальный элемент в конце списка + // или перед ним дырка => можем увеличить ранг без последствий + _, err := s.cl.ZIncrBy(ctx, queue.Key(), 1, entry.ChatID).Result() + if err != nil { + return fmt.Errorf("%s: %w", op, err) + } + } else { + // Элемент спереди найден => меняем им ранги + _, err = s.cl.ZIncrBy(ctx, queue.Key(), 1, entry.ChatID).Result() + if err != nil { + return fmt.Errorf("%s: %w", op, err) + } - _, err = s.cl.LSet(ctx, queue.Key(), pos+1, entry.ChatID).Result() - if err != nil { - return fmt.Errorf("%s: %w", op, err) + _, err = s.cl.ZIncrBy(ctx, queue.Key(), -1, ahead[0]).Result() + if err != nil { + return fmt.Errorf("%s: %w", op, err) + } } return nil @@ -175,7 +230,7 @@ func (s *Storage) Remove( ) error { const op = "redis.Remove" - _, err := s.cl.LRem(ctx, queue.Key(), 1, entry.ChatID).Result() + _, err := s.cl.ZRem(ctx, queue.Key(), entry.ChatID).Result() if err != nil { return fmt.Errorf("%s: %w", op, err) } diff --git a/internal/services/errors.go b/internal/services/errors.go index 2b15a33..cb79545 100644 --- a/internal/services/errors.go +++ b/internal/services/errors.go @@ -5,5 +5,5 @@ import "errors" var ( ErrNotFound = errors.New("resource not found") ErrAlreadyInQueue = errors.New("user already in queue") - ErrQueueEnd = errors.New("entry is at the end of the queue") + ErrPlaceTaken = errors.New("place in queue already taken") ) diff --git a/internal/services/queue/cache.go b/internal/services/queue/cache.go index a6e8787..ecce821 100644 --- a/internal/services/queue/cache.go +++ b/internal/services/queue/cache.go @@ -58,7 +58,7 @@ func (q *Queue) GetFromCache( slog.String("err", err.Error()), ) - if errors.Is(err, repositories.ErrCacheMiss) { + if errors.Is(err, repositories.ErrNotFound) { return models.Queue{}, fmt.Errorf("%s: %w", op, services.ErrNotFound) } return models.Queue{}, fmt.Errorf("%s: %w", op, err) diff --git a/internal/services/queue/cache_test.go b/internal/services/queue/cache_test.go index c9c1094..7cfebc1 100644 --- a/internal/services/queue/cache_test.go +++ b/internal/services/queue/cache_test.go @@ -97,7 +97,7 @@ func TestGetFromCache_CacheMiss(t *testing.T) { chatID := gofakeit.Int64() - cache.EXPECT().Get(ctx, fmt.Sprint(chatID)).Return("", repositories.ErrCacheMiss) + cache.EXPECT().Get(ctx, fmt.Sprint(chatID)).Return("", repositories.ErrNotFound) queue, err := service.GetFromCache(ctx, chatID) require.ErrorIs(t, err, services.ErrNotFound) diff --git a/internal/services/queue/queue.go b/internal/services/queue/queue.go index 2cddf50..bd855aa 100644 --- a/internal/services/queue/queue.go +++ b/internal/services/queue/queue.go @@ -79,6 +79,9 @@ func (q *Queue) Push( if errors.Is(err, repositories.ErrAlreadyInQueue) { return fmt.Errorf("%s: %w", op, services.ErrAlreadyInQueue) } + if errors.Is(err, repositories.ErrPlaceTaken) { + return fmt.Errorf("%s: %w", op, services.ErrPlaceTaken) + } return fmt.Errorf("%s: %w", op, err) } @@ -196,9 +199,9 @@ func (q *Queue) LetAhead( log.Info("Trying to let someone go ahead in queue") - pos, err := q.queuePos.GetPosition(ctx, queue, entry) + err := q.queueSwap.LetAhead(ctx, queue, entry) if err != nil { - log.Error("Failed to get entry position", + log.Error("Failed to let someone go ahead", slog.String("err", err.Error()), ) @@ -208,30 +211,6 @@ func (q *Queue) LetAhead( return fmt.Errorf("%s: %w", op, err) } - len, err := q.queueLength.Len(ctx, queue) - if err != nil { - // Нет смысла проверять на ErrNotFound, так как - // на данный момент мы уже получили позицию в очереди - log.Error("Failed to get queue length", - slog.String("err", err.Error()), - ) - return fmt.Errorf("%s: %w", op, err) - } - - if pos == len { - // Пользователь в конце очереди - не сможет пропустить - log.Warn("User is at the queue end") - return fmt.Errorf("%s: %w", op, services.ErrQueueEnd) - } - - err = q.queueSwap.LetAhead(ctx, queue, entry) - if err != nil { - log.Error("Failed to let someone go ahead", - slog.String("err", err.Error()), - ) - return fmt.Errorf("%s: %w", op, err) - } - log.Info("Successfully swapped with person ahead") return nil diff --git a/internal/services/queue/queue_test.go b/internal/services/queue/queue_test.go index 49d27a7..eaa76c3 100644 --- a/internal/services/queue/queue_test.go +++ b/internal/services/queue/queue_test.go @@ -81,9 +81,9 @@ func TestQueuePush_Success(t *testing.T) { Subject: gofakeit.Noun(), } - chatID := gofakeit.ID() entry := models.QueueEntry{ - ChatID: chatID, + Position: gofakeit.Int(), + ChatID: gofakeit.ID(), } queueBase.EXPECT().Push(ctx, subjectQueue, entry).Return(nil) @@ -100,9 +100,9 @@ func TestQueuePush_AlreadyInQueue(t *testing.T) { Subject: gofakeit.Noun(), } - chatID := gofakeit.ID() entry := models.QueueEntry{ - ChatID: chatID, + Position: gofakeit.Int(), + ChatID: gofakeit.ID(), } queueBase.EXPECT().Push(ctx, subjectQueue, entry).Return(repositories.ErrAlreadyInQueue) @@ -111,6 +111,36 @@ func TestQueuePush_AlreadyInQueue(t *testing.T) { require.ErrorIs(t, err, services.ErrAlreadyInQueue) } +func TestQueuePush_PlaceTaken(t *testing.T) { + service, ctx := newService(t) + + subjectQueue := models.Queue{ + Group: gofakeit.ID(), + Subject: gofakeit.Noun(), + } + + pos := gofakeit.Int() + + entry1 := models.QueueEntry{ + Position: pos, + ChatID: gofakeit.ID(), + } + + entry2 := models.QueueEntry{ + Position: pos, + ChatID: gofakeit.ID(), + } + + queueBase.EXPECT().Push(ctx, subjectQueue, entry1).Return(nil) + queueBase.EXPECT().Push(ctx, subjectQueue, entry2).Return(repositories.ErrPlaceTaken) + + err := service.Push(ctx, subjectQueue, entry1) + require.Empty(t, err) + + err = service.Push(ctx, subjectQueue, entry2) + require.ErrorIs(t, err, services.ErrPlaceTaken) +} + func TestQueuePush_Failure(t *testing.T) { service, ctx := newService(t) @@ -119,9 +149,9 @@ func TestQueuePush_Failure(t *testing.T) { Subject: gofakeit.Noun(), } - chatID := gofakeit.ID() entry := models.QueueEntry{ - ChatID: chatID, + Position: gofakeit.Int(), + ChatID: gofakeit.ID(), } expectedErr := errors.New("внезапная ошибка на стороне базы данных") @@ -141,9 +171,9 @@ func TestQueuePop_Success(t *testing.T) { Subject: gofakeit.Noun(), } - chatID := gofakeit.ID() entry := models.QueueEntry{ - ChatID: chatID, + Position: gofakeit.Int(), + ChatID: gofakeit.ID(), } // Попаем айдишник @@ -243,9 +273,9 @@ func TestQueueRange_Success(t *testing.T) { } entries := []models.QueueEntry{ - {ChatID: gofakeit.ID()}, - {ChatID: gofakeit.ID()}, - {ChatID: gofakeit.ID()}, + {Position: gofakeit.Int(), ChatID: gofakeit.ID()}, + {Position: gofakeit.Int(), ChatID: gofakeit.ID()}, + {Position: gofakeit.Int(), ChatID: gofakeit.ID()}, } queueViewer.EXPECT().Range(ctx, subjectQueue, queueRange).Return(entries, nil) @@ -297,7 +327,8 @@ func TestQueueRemove_Success(t *testing.T) { } entry := models.QueueEntry{ - ChatID: gofakeit.ID(), + Position: gofakeit.Int(), + ChatID: gofakeit.ID(), } queueRemover.EXPECT().Remove(ctx, subjectQueue, entry).Return(nil) @@ -315,7 +346,8 @@ func TestQueueRemove_Failure(t *testing.T) { } entry := models.QueueEntry{ - ChatID: gofakeit.ID(), + Position: gofakeit.Int(), + ChatID: gofakeit.ID(), } expectedErr := errors.New("внезапная ошибка на стороне базы данных") @@ -336,11 +368,10 @@ func TestQueueLetAhead_Success(t *testing.T) { } entry := models.QueueEntry{ - ChatID: gofakeit.ID(), + Position: gofakeit.Int(), + ChatID: gofakeit.ID(), } - queuePos.EXPECT().GetPosition(ctx, subjectQueue, entry).Return(2, nil) - queueLength.EXPECT().Len(ctx, subjectQueue).Return(5, nil) queueSwap.EXPECT().LetAhead(ctx, subjectQueue, entry).Return(nil) err := service.LetAhead(ctx, subjectQueue, entry) @@ -356,75 +387,17 @@ func TestQueueLetAhead_NotFound(t *testing.T) { } entry := models.QueueEntry{ - ChatID: gofakeit.ID(), + Position: gofakeit.Int(), + ChatID: gofakeit.ID(), } - queuePos.EXPECT().GetPosition(ctx, subjectQueue, entry).Return(0, repositories.ErrNotFound) + queueSwap.EXPECT().LetAhead(ctx, subjectQueue, entry).Return(repositories.ErrNotFound) err := service.LetAhead(ctx, subjectQueue, entry) require.ErrorIs(t, err, services.ErrNotFound) } -func TestQueueLetAhead_QueueEnd(t *testing.T) { - service, ctx := newService(t) - - subjectQueue := models.Queue{ - Group: gofakeit.ID(), - Subject: gofakeit.Noun(), - } - - entry := models.QueueEntry{ - ChatID: gofakeit.ID(), - } - - queuePos.EXPECT().GetPosition(ctx, subjectQueue, entry).Return(5, nil) - queueLength.EXPECT().Len(ctx, subjectQueue).Return(5, nil) - - err := service.LetAhead(ctx, subjectQueue, entry) - require.NotEmpty(t, err) - assert.ErrorIs(t, err, services.ErrQueueEnd) -} - -func TestQueueLetAhead_Failure1(t *testing.T) { - service, ctx := newService(t) - - subjectQueue := models.Queue{ - Group: gofakeit.ID(), - Subject: gofakeit.Noun(), - } - - entry := models.QueueEntry{ - ChatID: gofakeit.ID(), - } - - expectedErr := errors.New("внезапная ошибка на стороне базы данных") - queuePos.EXPECT().GetPosition(ctx, subjectQueue, entry).Return(0, expectedErr) - - err := service.LetAhead(ctx, subjectQueue, entry) - require.ErrorIs(t, err, expectedErr) -} - -func TestQueueLetAhead_Failure2(t *testing.T) { - service, ctx := newService(t) - - subjectQueue := models.Queue{ - Group: gofakeit.ID(), - Subject: gofakeit.Noun(), - } - - entry := models.QueueEntry{ - ChatID: gofakeit.ID(), - } - - expectedErr := errors.New("внезапная ошибка на стороне базы данных") - queuePos.EXPECT().GetPosition(ctx, subjectQueue, entry).Return(1, nil) - queueLength.EXPECT().Len(ctx, subjectQueue).Return(0, expectedErr) - - err := service.LetAhead(ctx, subjectQueue, entry) - require.ErrorIs(t, err, expectedErr) -} - -func TestQueueLetAhead_Failure3(t *testing.T) { +func TestQueueLetAhead_Failure(t *testing.T) { service, ctx := newService(t) subjectQueue := models.Queue{ @@ -433,12 +406,11 @@ func TestQueueLetAhead_Failure3(t *testing.T) { } entry := models.QueueEntry{ - ChatID: gofakeit.ID(), + Position: gofakeit.Int(), + ChatID: gofakeit.ID(), } expectedErr := errors.New("внезапная ошибка на стороне базы данных") - queuePos.EXPECT().GetPosition(ctx, subjectQueue, entry).Return(2, nil) - queueLength.EXPECT().Len(ctx, subjectQueue).Return(5, nil) queueSwap.EXPECT().LetAhead(ctx, subjectQueue, entry).Return(expectedErr) err := service.LetAhead(ctx, subjectQueue, entry) @@ -456,7 +428,8 @@ func TestQueueGetPosition_Success(t *testing.T) { } entry := models.QueueEntry{ - ChatID: gofakeit.ID(), + Position: gofakeit.Int(), + ChatID: gofakeit.ID(), } expectedPos := int64(3) @@ -476,7 +449,8 @@ func TestQueueGetPosition_NotFound(t *testing.T) { } entry := models.QueueEntry{ - ChatID: gofakeit.ID(), + Position: gofakeit.Int(), + ChatID: gofakeit.ID(), } queuePos.EXPECT().GetPosition(ctx, subjectQueue, entry).Return(0, repositories.ErrNotFound) @@ -495,7 +469,8 @@ func TestQueueGetPosition_Failure(t *testing.T) { } entry := models.QueueEntry{ - ChatID: gofakeit.ID(), + Position: gofakeit.Int(), + ChatID: gofakeit.ID(), } expectedErr := errors.New("внезапная ошибка на стороне базы данных")