package metrics import ( "strings" "testing" "github.com/prometheus/client_golang/prometheus" ) // Метка метрики уезжает на страницу, которая отдаётся без проверки отправителя, // поэтому судим здесь ровно одно: что наружу выходит только известное значение. func TestFormatLabel(t *testing.T) { testCases := []struct { name string ext string want string }{ {name: "известное расширение с точкой", ext: ".mp3", want: "mp3"}, {name: "известное расширение без точки", ext: "mp3", want: "mp3"}, {name: "регистр приводится", ext: ".MP3", want: "mp3"}, {name: "умолчание сервиса", ext: ".audio", want: "audio"}, // Голосовое из Telegram приходит путём вида `voice/file_N.oga`, кружок — // с `.mp4`. Это основной вход сервиса: усечение перечня до списка, по // которому бот отбирает документы, схлопнуло бы его в общее значение. {name: "голосовое из Telegram", ext: ".oga", want: "oga"}, {name: "видеокружок из Telegram", ext: ".mp4", want: "mp4"}, {name: "opus", ext: ".opus", want: "opus"}, // Значение сверяется с литералом, а не с самой константой: сверка с // константой утверждала бы тавтологию, а спека нормирует слово `other` // дословно. {name: "хвост имени отправителя", ext: ".тайное-слово", want: "other"}, {name: "часть даты в имени", ext: ".08", want: "other"}, {name: "пустое", ext: "", want: "other"}, {name: "одна точка", ext: ".", want: "other"}, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { if got := FormatLabel(tc.ext); got != tc.want { t.Errorf("FormatLabel(%q) = %q, ожидалось %q", tc.ext, got, tc.want) } }) } } // labelValues собирает значения меток названного семейства из общего реестра. func labelValues(t *testing.T, family string) []string { families, err := prometheus.DefaultGatherer.Gather() if err != nil { t.Fatalf("не удалось прочитать реестр метрик: %v", err) } var values []string for _, mf := range families { if mf.GetName() != family { continue } for _, m := range mf.GetMetric() { for _, label := range m.GetLabel() { values = append(values, label.GetValue()) } } } return values } // Метку конвертации приёмом по HTTP не достать: шаг живёт в воркере, и своего // окружения у него нет. Поэтому обёртка судится здесь — по реестру, а не по // чистой функции: сырое употребление гистограммы мимо обёртки и есть то место, // где хвост имени отправителя однажды снова уедет наружу. func TestObserveConversionDurationLabelsAreKnown(t *testing.T) { const tail = "CONVLEAKMARKER5X8" ObserveConversionDuration("."+tail, "ogg", false, 1) values := labelValues(t, "transcriber_conversion_duration_seconds") if len(values) == 0 { t.Fatal("метрика длительности конвертации не заполнилась") } assertTailAbsentAndOtherPresent(t, values, tail) } // Та же проверка для метки приёма — на случай, если обёртку обойдут только с // одной стороны. func TestObserveInputFileSizeLabelsAreKnown(t *testing.T) { const tail = "INPUTLEAKMARKER5X8" ObserveInputFileSize("."+tail, 42) values := labelValues(t, "transcriber_input_file_size_bytes") if len(values) == 0 { t.Fatal("метрика размера принятой записи не заполнилась") } assertTailAbsentAndOtherPresent(t, values, tail) } // assertTailAbsentAndOtherPresent судит значения метки по двум признакам сразу. // Одного «хвоста нет» мало: приведение, ослабленное до смены регистра, хвост // пропустило бы, а поиск заглавного маркера в строчном значении его не нашёл бы. // Поэтому сравнение регистронезависимое, и рядом стоит второй признак — что // незнакомое расширение вообще доехало до общего значения. func assertTailAbsentAndOtherPresent(t *testing.T, values []string, tail string) { t.Helper() for _, v := range values { if strings.Contains(strings.ToLower(v), strings.ToLower(tail)) { t.Fatalf("метка несёт хвост имени, данного отправителем: %q", v) } } for _, v := range values { if v == "other" { return } } t.Fatal("незнакомое расширение не приведено к общему значению") }