package hae_test import ( "encoding/json" "os" "path/filepath" "testing" "git.vakhrushev.me/av/healthlog/internal/hae" ) func TestPointValueФормыТочки(t *testing.T) { t.Parallel() cases := []struct { name string raw string want float64 ok bool }{ {"обычная точка", `{"qty":72.5,"date":"2026-07-31 12:00:00 +0300"}`, 72.5, true}, {"пульс без qty", `{"Min":60,"Avg":70.25,"Max":80}`, 70.25, true}, {"qty впереди Avg", `{"qty":1,"Avg":9}`, 1, true}, {"ноль — значение", `{"qty":0}`, 0, true}, {"отрицательное значение", `{"qty":-1.5}`, -1.5, true}, {"нет числовых полей", `{"date":"…","source":"часы"}`, 0, false}, {"qty равен null", `{"qty":null,"Avg":3}`, 3, true}, {"qty строкой", `{"qty":"72.5"}`, 0, false}, {"qty объектом", `{"qty":{"value":1}}`, 0, false}, {"переполнение float64", `{"qty":1e400}`, 0, false}, {"точка не объект", `[1,2,3]`, 0, false}, {"пустой объект", `{}`, 0, false}, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { t.Parallel() got, ok := hae.PointValue(json.RawMessage(c.raw)) if ok != c.ok { t.Fatalf("наличие значения: получили %v, ждали %v", ok, c.ok) } if ok && got != c.want { t.Errorf("значение: получили %v, ждали %v", got, c.want) } }) } } // Формы точки берутся из реальных пакетов: документация формата тонкая и // местами расходится с тем, что приложение шлёт (docs/research/apple-health.md). func TestPointValueНаРеальныхПакетах(t *testing.T) { t.Parallel() for _, name := range []string{"minute.json", "hour.json", "raw.json"} { t.Run(name, func(t *testing.T) { t.Parallel() body, err := os.ReadFile(filepath.Join("testdata", name)) if err != nil { t.Fatalf("фикстура: %v", err) } var doc struct { Data struct { Metrics []struct { Name string `json:"name"` Data []json.RawMessage `json:"data"` } `json:"metrics"` } `json:"data"` } if err := json.Unmarshal(body, &doc); err != nil { t.Fatalf("разбор фикстуры: %v", err) } total, valued := 0, 0 for _, m := range doc.Data.Metrics { for _, raw := range m.Data { total++ if _, ok := hae.PointValue(raw); ok { valued++ } } } if total == 0 { t.Fatal("в фикстуре нет точек — проверять нечего") } // Утверждается свойство, а не число: корпус фикстур пополняется, и // константа протухла бы молча. Значение несёт подавляющее // большинство точек — если вдруг перестанет, это видно сразу. if valued*2 < total { t.Errorf("значение прочиталось лишь у %d точек из %d", valued, total) } t.Logf("точек %d, со значением %d", total, valued) }) } }