Дозакрыты находки ревью по слиянию сущностей
- Правило покрытия получило второй разряд (условный, как у точек), запрет вырождения формы и счёт содержательных элементов ряда: скелет из скаляров и ряд из null больше не затирают маршрут. Победитель внутри доставки стал функцией множества версий — общим помощником с точками, — а провенанс поднимается и при совпавшем хеше, иначе отложенная доставка возвращала витрину к прежнему содержимому. - Одно поле не того типа больше не уносит сущность, а пропуски видны в учётной записи доставки (миграция 00008, NULL = «не измерялось»); каноническая форма считается один раз и вне транзакции; откат бинаря поверх новой схемы отказывает на старте; текст ошибки разбора не несёт значений из тела. - Ревью кода профилем deep (девять проходов) нашло две регрессии и обе закрыты: безусловный второй разряд запирал законный досчёт навсегда, а выбор победителя был квадратичен по числу присланных версий одного ключа.
This commit is contained in:
@@ -702,3 +702,87 @@ func TestParseНепокрытыеСекцииГраницыИДетермини
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Инвариант «тела запросов только на DEBUG и с обрезкой» обходился одним `%v`:
|
||||
// строка в 8 МиБ на месте ожидаемого объекта давала текст ошибки в 8 МиБ, и он
|
||||
// уезжал атрибутом `error` на уровень WARN — то есть содержимое доставки
|
||||
// оказывалось в логе целиком и без обрезки.
|
||||
//
|
||||
// Предел держит само сообщение, а не обрезка на стороне логирующего: обрезка
|
||||
// живёт в другом месте и о новой ошибке разбора не узнает.
|
||||
func TestParseОшибкаНеНесётЗначенийИзТела(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const secret = "СЕКРЕТНОЕ-ЗНАЧЕНИЕ-ИЗ-ТЕЛА"
|
||||
cases := map[string]string{
|
||||
"строка вместо data": `{"data":"` + secret + strings.Repeat("A", 1<<20) + `"}`,
|
||||
"строка вместо тела": `"` + secret + strings.Repeat("A", 1<<20) + `"`,
|
||||
"число вместо имени секции": `{"data":{"metrics":[]},"` + secret + `":1}`,
|
||||
"число вместо содержимого": `{"data":{"metrics":` + strings.Repeat("9", 1<<20) + `}}`,
|
||||
}
|
||||
for name, body := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, err := hae.Parse([]byte(body), hae.Meta{})
|
||||
if err == nil {
|
||||
t.Skip("вход разобрался — проверять нечего")
|
||||
}
|
||||
msg := err.Error()
|
||||
if len(msg) > 512 {
|
||||
t.Errorf("текст ошибки %d Б: длина зависит от длины значения во входе", len(msg))
|
||||
}
|
||||
if strings.Contains(msg, secret) {
|
||||
t.Errorf("значение из тела доехало до сообщения: %s", msg)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Смещение обязано указывать на место ПЕРЕД виновным токеном, а не за ним.
|
||||
// Декодер сообщает позицию как конец последнего возвращённого токена, поэтому
|
||||
// снятая ПОСЛЕ чтения она отличалась бы от начала проблемы ровно на длину
|
||||
// значения — на восемь мегабайт в том самом случае, ради которого требование и
|
||||
// написано. Проверяется само свойство: смещение не зависит от длины значения.
|
||||
func TestParseОшибкаНазываетТипТокенаИНачало(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
short := `{"data":"` + strings.Repeat("A", 16) + `"}`
|
||||
long := `{"data":"` + strings.Repeat("A", 1<<20) + `"}`
|
||||
|
||||
msgs := make([]string, 0, 2)
|
||||
for _, body := range []string{short, long} {
|
||||
_, err := hae.Parse([]byte(body), hae.Meta{})
|
||||
if err == nil {
|
||||
t.Fatal("ожидалась ошибка")
|
||||
}
|
||||
msgs = append(msgs, err.Error())
|
||||
}
|
||||
|
||||
if !strings.Contains(msgs[0], "string") {
|
||||
t.Errorf("тип токена не назван словарём JSON: %s", msgs[0])
|
||||
}
|
||||
// `{"data"` — семь байт: разбор виновного значения начинается здесь.
|
||||
if !strings.Contains(msgs[0], "смещении 7") {
|
||||
t.Errorf("смещение не указывает на место перед токеном: %s", msgs[0])
|
||||
}
|
||||
if msgs[0] != msgs[1] {
|
||||
t.Errorf("смещение поехало вместе с длиной значения:\n %s\n %s", msgs[0], msgs[1])
|
||||
}
|
||||
}
|
||||
|
||||
// Ошибка может прийти не только от нашего разбора, но и из encoding/json, а его
|
||||
// UnmarshalTypeError кладёт в текст ЛИТЕРАЛ значения: тело из миллиона цифр
|
||||
// давало текст ошибки в мегабайт. Предел держится на границе пакета.
|
||||
func TestParseЧужаяПричинаОбрезается(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
body := `{"data":{"metrics":` + strings.Repeat("9", 1<<20) + `}}`
|
||||
_, err := hae.Parse([]byte(body), hae.Meta{})
|
||||
if err == nil {
|
||||
t.Fatal("ожидалась ошибка")
|
||||
}
|
||||
if len(err.Error()) > 512 {
|
||||
t.Errorf("текст ошибки %d Б: литерал из тела доехал до сообщения", len(err.Error()))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user