Files
av 8db2ec7ff4 Цена читающего маршрута: чекпойнт WAL по таймеру и условный запрос
- рядом с воркером свёртки живёт горутина, раз в минуту разбирающая журнал
  пассивным чекпойнтом; «журнал не разбирается» видно строкой владельцу, а не
  только по `df`. Признак — пара чисел, а не флаг занятости: тот молчит под
  удерживаемым читателем (`busy=0` при 6256 страницах и пяти перенесённых), а
  при занятой блокировке отдаёт `-1` вместо ответа, и `-1 >= -1` читалось бы как
  «разобрано целиком»
- каталог отвечает `304` на `If-None-Match`, не открывая снимок витрины. Метка
  собрана из всего, от чего зависит ответ: версии витрины (`data_version` с
  закреплённого соединения плюс поколение — значение локально для соединения и
  не переживает переоткрытия), горизонта измерения и области действия ресурса.
  Версия снимается до и после сборки: снятая после пометила бы устаревший снимок
  свежим номером
- предел и дедлайн ответа отложены в задачу Read API точек вместе с измеренной
  ценой первого запроса; попутно починен флаки-тест чужой задачи, искавший
  значение точки в сыром буфере записи лога
2026-08-02 20:42:22 +03:00

73 lines
3.2 KiB
Go

package httpapi
import (
"net/http/httptest"
"testing"
)
// Сравнение меток слабое, звёздочка совпадает с любой существующей меткой, а
// мусор условия не выполняет — и это не отказ: клиент, приславший кривой
// заголовок, получает данные, а не `400`.
func TestУсловиеЗапроса(t *testing.T) {
t.Parallel()
const tag = `W/"metrics.gen-7"`
cases := []struct {
name string
header []string
tag string
want bool
}{
{"заголовка нет", nil, tag, false},
{"пустая строка", []string{""}, tag, false},
{"та же метка", []string{tag}, tag, true},
{"та же метка без W/", []string{`"metrics.gen-7"`}, tag, true},
{"чужая метка", []string{`W/"metrics.gen-8"`}, tag, false},
{"метка другого ресурса", []string{`W/"points.gen-7"`}, tag, false},
{"список, метка вторая", []string{`W/"metrics.gen-1", W/"metrics.gen-7"`}, tag, true},
{"две строки заголовка", []string{`W/"metrics.gen-1"`, `W/"metrics.gen-7"`}, tag, true},
{"запятая внутри метки", []string{`W/"points.a,b-7"`}, `W/"points.a,b-7"`, true},
{"метка без закрывающей кавычки", []string{`W/"metrics.gen-7`}, tag, false},
{"звёздочка", []string{"*"}, tag, true},
{"звёздочка без метки", []string{"*"}, "", false},
{"мусор", []string{"metrics.gen-7"}, tag, false},
{"мусор со звёздочкой внутри", []string{`"*"`}, tag, false},
{"метки нет", []string{tag}, "", false},
}
for _, c := range cases {
if got := notModified(c.header, c.tag); got != c.want {
t.Errorf("%s: %v, ждали %v", c.name, got, c.want)
}
}
}
// Пустая версия метки не даёт: подписать ответ нечем, и притворяться нельзя.
func TestМеткаИзВерсии(t *testing.T) {
t.Parallel()
if got := etag("metrics", ""); got != "" {
t.Errorf("пустая версия дала метку %q", got)
}
if got := etag("metrics", "gen-7"); got != `W/"metrics.gen-7"` {
t.Errorf("метка %q, ждали слабую с областью", got)
}
if etag("metrics", "gen-7") == etag("points", "gen-7") {
t.Error("метки разных ресурсов совпали — 304 отдал бы чужие данные")
}
}
// Подписать нечем — заголовка нет вовсе. Пустой `ETag:` синтаксически невалиден,
// и что с ним сделает посредник, не определено ничем.
func TestОтветБезВерсииНеНесётМетки(t *testing.T) {
t.Parallel()
rec := httptest.NewRecorder()
setReadHeaders(rec, "")
if _, ok := rec.Header()["Etag"]; ok {
t.Errorf("ответ без версии несёт метку %q", rec.Header().Get("ETag"))
}
if got := rec.Header().Get("Cache-Control"); got != "private, no-cache" {
t.Errorf("правило кеширования %q — оно не зависит от наличия метки", got)
}
}