непокрытые секции доставки видны в статусе разбора
- половина потока (50 доставок из 104) не несёт metrics вовсе и до сих пор числилась parsed: ретеншен, поверив статусу, срезал бы тела stateOfMind, которых в экспорте Apple нет - разбор перечисляет верхнеуровневые ключи data, непокрытые проглатываются декодированием: тело 40 МиБ из непокрытой секции удерживает 0 МиБ - статус partial и колонка delivery.uncovered_sections; миграция переводит прежние parsed в pending — им верить нельзя - витрина не изменилась: отпечаток совпал с прогоном до изменения
This commit is contained in:
@@ -182,3 +182,75 @@ func TestFoldНесравнимыеНаборыДаютWarn(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Имена непокрытых секций в логе нужны — по ним видно, что поток принёс новое.
|
||||
// Содержимого секций там быть не может: это данные о здоровье. И уровень от
|
||||
// самой частичности не растёт — `partial` установившееся состояние половины
|
||||
// потока, а постоянный WARN каждые пять минут обесценивает уровень.
|
||||
func TestFoldЧастичныйРазборВЛоге(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var buf bytes.Buffer
|
||||
log := slog.New(slog.NewJSONHandler(&buf, &slog.HandlerOptions{Level: slog.LevelInfo}))
|
||||
|
||||
dir := t.TempDir()
|
||||
arch, err := archive.New(filepath.Join(dir, "raw"))
|
||||
if err != nil {
|
||||
t.Fatalf("архив: %v", err)
|
||||
}
|
||||
st, err := store.Open(filepath.Join(dir, "healthlog.db"))
|
||||
if err != nil {
|
||||
t.Fatalf("база: %v", err)
|
||||
}
|
||||
t.Cleanup(func() { _ = st.Close() })
|
||||
|
||||
f := fold.New(arch, st, 0, log)
|
||||
ctx := context.Background()
|
||||
|
||||
// Содержимое непокрытой секции помечено так, чтобы его нельзя было спутать
|
||||
// ни с чем: если оно окажется в логе, тест это увидит.
|
||||
const body = `{"data":{
|
||||
"metrics":[{"name":"step_count","units":"count","data":[
|
||||
{"date":"2025-06-05 10:00:00 +0300","qty":1},
|
||||
{"date":"2025-06-05 10:01:00 +0300","qty":2},
|
||||
{"date":"2025-06-05 10:02:00 +0300","qty":3},
|
||||
{"date":"2025-06-05 10:03:00 +0300","qty":4},
|
||||
{"date":"2025-06-05 10:04:00 +0300","qty":5},
|
||||
{"date":"2025-06-05 10:05:00 +0300","qty":6},
|
||||
{"date":"2025-06-05 10:06:00 +0300","qty":7},
|
||||
{"date":"2025-06-05 10:07:00 +0300","qty":8},
|
||||
{"date":"2025-06-05 10:08:00 +0300","qty":9},
|
||||
{"date":"2025-06-05 10:09:00 +0300","qty":10}]}],
|
||||
"stateOfMind":[{"valence":"СЕКРЕТНОЕ-НАСТРОЕНИЕ"}]}}`
|
||||
|
||||
deliver(t, arch, st, "d1", "Minutes", "a1", []byte(body))
|
||||
if _, err := f.Fold(ctx, "d1"); err != nil {
|
||||
t.Fatalf("свёртка: %v", err)
|
||||
}
|
||||
|
||||
out := buf.String()
|
||||
if !strings.Contains(out, "stateOfMind") {
|
||||
t.Error("имени непокрытой секции нет в логе — момент появления новой секции незаметен")
|
||||
}
|
||||
if strings.Contains(out, "СЕКРЕТНОЕ-НАСТРОЕНИЕ") {
|
||||
t.Error("содержимое непокрытой секции утекло в лог")
|
||||
}
|
||||
|
||||
var rec struct {
|
||||
Level string `json:"level"`
|
||||
Uncovered []string `json:"uncovered"`
|
||||
}
|
||||
line := strings.TrimSpace(out)
|
||||
if i := strings.LastIndex(line, "\n"); i >= 0 {
|
||||
line = line[i+1:]
|
||||
}
|
||||
if err := json.Unmarshal([]byte(line), &rec); err != nil {
|
||||
t.Fatalf("запись лога не разбирается: %v", err)
|
||||
}
|
||||
if rec.Level != "INFO" {
|
||||
t.Errorf("уровень %q, ожидался INFO: частичность — не отклонение", rec.Level)
|
||||
}
|
||||
if len(rec.Uncovered) != 1 || rec.Uncovered[0] != "stateOfMind" {
|
||||
t.Errorf("атрибут uncovered = %v, ожидался структурный список из stateOfMind", rec.Uncovered)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user