Files
av 63bffe2865 Приём отвечает 200 до свёртки, свёртку ведёт фоновый воркер
- Очередью служит сама таблица: доставка ждёт свёртки в статусе `pending`,
  канал несёт только бит «есть работа». Переполнять нечего, падение процесса
  очередь не теряет, а подбор `pending` при старте — обычный проход воркера, а
  не отдельный код. Классификация исхода общая с пересборкой журнала.
- Исход разбора начал отражать доставку, а не обстоятельства: отмена и
  занятость базы статус не меняют (иначе конкуренция за базу выводила бы
  доставку из очереди навсегда), паника свёртки больше не валит процесс, а
  учёт доставки идёт через транзакцию с повторами.
- Длинный бюджет ответа выдан маршруту приёма, а не всему серверу:
  `write_timeout` в Go покрывает и чтение тела, и общий подъём снял бы защиту с
  остальных маршрутов.
2026-08-02 11:01:42 +03:00

68 lines
3.0 KiB
Go

package replay
import (
"context"
"errors"
"fmt"
"testing"
"git.vakhrushev.me/av/healthlog/internal/fold"
"git.vakhrushev.me/av/healthlog/internal/hae"
"git.vakhrushev.me/av/healthlog/internal/store"
)
// Классификация исхода — та половина, которую пересборка и фоновый воркер
// обязаны делить. Проверяется перебором классов, без базы и без архива: второй
// классификатор разошёлся бы с первым молча, а по счётчику `partial`
// принимается решение о судьбе тела в архиве.
func TestClassifyРазводитИсходыПоКлассам(t *testing.T) {
t.Parallel()
cases := []struct {
name string
err error
want Outcome
}{
{"успех", nil, Outcome{Folded: 1}},
{"слой не выведен", hae.ErrLayerUnknown, Outcome{FailedLayer: 1}},
{"слой не выведен, обёрнут", fmt.Errorf("свёртка: %w", hae.ErrLayerUnknown), Outcome{FailedLayer: 1}},
{"содержимое не разбирается", hae.ErrMalformed, Outcome{FailedMalformed: 1}},
{"база занята", store.ErrBusy, Outcome{Deferred: 1}},
{"база занята, обёрнута", fmt.Errorf("слияние: %w", store.ErrBusy), Outcome{Deferred: 1}},
{"работу прекратили снаружи", context.Canceled, Outcome{Deferred: 1}},
// Дедлайн — свойство доставки, а не обстоятельств: она не уложится в
// бюджет и в следующий раз, а повтор безнадёжного останавливает очередь.
{"свёртка не уложилась в бюджет", context.DeadlineExceeded, Outcome{FailedOther: 1}},
{"прочее", errors.New("диск отвалился"), Outcome{FailedOther: 1}},
// Паника — дефект нашего кода, а не обстоятельство: доставка выводится
// из очереди, тело ждёт пересборки.
{"свёртка паниковала", fold.ErrPanicked, Outcome{FailedOther: 1}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
t.Parallel()
if got := classify(c.err); got != c.want {
t.Errorf("Classify(%v) = %+v, ожидалось %+v", c.err, got, c.want)
}
})
}
}
// Накопление — сложение по классам: у пересборки и у воркера один набор имён
// для одних исходов.
func TestOutcomeAddСкладываетПоКлассам(t *testing.T) {
t.Parallel()
var total Outcome
total.Add(Outcome{Folded: 1, Partial: 1})
total.Add(Outcome{Folded: 1, Incomparable: 2})
total.Add(Outcome{Deferred: 1})
want := Outcome{Folded: 2, Deferred: 1, Partial: 1, Incomparable: 2}
if total != want {
t.Errorf("сумма %+v, ожидалась %+v", total, want)
}
}