package healthkit_test import ( "strings" "testing" "git.vakhrushev.me/av/healthlog/internal/healthkit" ) // Обе формы имени, которые Apple дала одному значению, обязаны сойтись в один // код: иначе история расколется вторично, уже на «стабильной» стороне // (находка 43). func TestCanonicalОбеФормыСходятся(t *testing.T) { t.Parallel() const ( old = "HKCategoryValueSleepAnalysisAsleep" now = "HKCategoryValueSleepAnalysisAsleepUnspecified" ) if got := healthkit.Canonical(old); got != now { t.Errorf("устаревшее имя %q дало %q, ожидалось %q", old, got, now) } if got := healthkit.Canonical(now); got != now { t.Errorf("нынешнее имя %q дало %q, ожидалось %q", now, got, now) } } func TestCanonicalКодБезСинонимаОстаётсяСобой(t *testing.T) { t.Parallel() const code = "HKCategoryValueSleepAnalysisAsleepREM" if got := healthkit.Canonical(code); got != code { t.Errorf("код без синонима стал %q", got) } if got := healthkit.Canonical(""); got != "" { t.Errorf("пустой код стал %q", got) } } // Плоскость таблицы синонимов — то, чем оправдано отсутствие обхода цепочек в // рантайме. Проверяется по таблице целиком, а не на примере: правило держится // на всей таблице, и первая же добавленная запись может его нарушить. // // Обходим через Canonical, а не через саму карту: наружу она не отдаётся, а // «значение не является ключом» проверяемо снаружи — канонизация значения // обязана быть неподвижной точкой. func TestSynonymsТаблицаПлоская(t *testing.T) { t.Parallel() // Значения таблицы наблюдаемы через Code: словарь уже канонизирован, а // коды, которые он выдаёт, обязаны быть неподвижными точками. for _, value := range []string{"Основная", "Бодрствование", "БДГ", "Глубокий", "В кровати", "Во сне"} { code := healthkit.Code("ru", value) if code == "" { t.Fatalf("измеренная строка %q кода не дала", value) } if again := healthkit.Canonical(code); again != code { t.Errorf("код %q не неподвижная точка: канонизация дала %q — таблица синонимов не плоская", code, again) } } // То же для известного алиаса: его канонизация обязана быть неподвижной с // одного шага. once := healthkit.Canonical("HKCategoryValueSleepAnalysisAsleep") if twice := healthkit.Canonical(once); twice != once { t.Errorf("алиас разрешился не за один шаг: %q → %q", once, twice) } } func TestCodeФазыСна(t *testing.T) { t.Parallel() cases := []struct { locale string value string want string }{ {"ru", "Основная", "HKCategoryValueSleepAnalysisAsleepCore"}, {"ru", "Бодрствование", "HKCategoryValueSleepAnalysisAwake"}, {"ru", "БДГ", "HKCategoryValueSleepAnalysisAsleepREM"}, {"ru", "Глубокий", "HKCategoryValueSleepAnalysisAsleepDeep"}, {"ru", "В кровати", "HKCategoryValueSleepAnalysisInBed"}, {"ru", "Во сне", "HKCategoryValueSleepAnalysisAsleepUnspecified"}, // Локали нет — код всё равно выводится вторым разрядом: заголовков в // сыром архиве не лежит, и усыновлённое тело обязано дать то же // состояние. {"", "Во сне", "HKCategoryValueSleepAnalysisAsleepUnspecified"}, // Незнакомая локаль знакомой строки код не отменяет. {"en", "Во сне", "HKCategoryValueSleepAnalysisAsleepUnspecified"}, // Догадок нет. {"ru", "Полудрёма", ""}, {"ru", "", ""}, {"", "Сидячий образ жизни", ""}, } for _, c := range cases { t.Run(c.locale+"/"+c.value, func(t *testing.T) { t.Parallel() if got := healthkit.Code(c.locale, c.value); got != c.want { t.Errorf("Code(%q, %q) = %q, ожидалось %q", c.locale, c.value, got, c.want) } }) } } func TestLocaleНормализация(t *testing.T) { t.Parallel() cases := []struct { name string header string want string }{ {"измеренное значение потока", "ru", "ru"}, {"верхний регистр", "RU", "ru"}, {"подтег", "ru-RU", "ru"}, {"список с весами и регистром", "RU-ru,ru;q=0.9,en;q=0.8", "ru"}, {"пробелы вокруг", " ru ", "ru"}, {"вес без списка", "ru;q=1", "ru"}, {"звёздочка тегом не является", "*", ""}, {"пустой заголовок", "", ""}, {"мусор", "!!!", ""}, {"однобуквенный тег", "r", ""}, {"непомерно длинный тег", strings.Repeat("x", 64), ""}, {"сто тегов", strings.Repeat("en,", 100) + "ru", "en"}, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { t.Parallel() if got := healthkit.Locale(c.header); got != c.want { t.Errorf("Locale(%q) = %q, ожидалось %q", c.header, got, c.want) } }) } }