package api import ( "net/http" "net/http/httptest" "reflect" "strings" "testing" "time" ) func TestClinicFrom(t *testing.T) { if c, ok := clinicFrom(""); !ok || c.Key != clinicDANA { t.Fatalf("пустая клиника должна быть DANA: %v %v", c, ok) } if c, ok := clinicFrom(" Ochevidno "); !ok || c.Key != "ochevidno" { t.Fatalf("очевидно не распознано: %v %v", c, ok) } if _, ok := clinicFrom("glaz"); ok { t.Fatal("неизвестная клиника принята") } } // Портал Битрикс24 общий: один источник лида не может считаться сразу в двух клиниках, // а каналы источников должны быть известными листами отчёта. func TestClinicBitrixSourcesDisjoint(t *testing.T) { owner := map[string]string{} for _, c := range clinicList { for src, ch := range c.bitrixSources { if prev, dup := owner[src]; dup { t.Errorf("источник %s есть и у %s, и у %s", src, prev, c.Key) } owner[src] = c.Key if _, known := knownChannels[ch]; !known { t.Errorf("%s: источник %s ведёт в неизвестный канал %q", c.Key, src, ch) } } for src := range c.bitrixGeneral { if prev, dup := owner[src]; dup { t.Errorf("общий источник %s уже у %s", src, prev) } owner[src] = c.Key } } } func TestProdoctorovUploadPerClinic(t *testing.T) { from := time.Date(2026, 9, 1, 0, 0, 0, 0, moscow) to := time.Date(2026, 10, 1, 0, 0, 0, 0, moscow) och := clinicByKey["ochevidno"] ochFile := prodoctorovPhoneBook(t, "Офтальмологический центр «Очевидно»", "запись на приём (телефон) - сентября 2026 ", []string{"31", "10.09.2026 12:30", "79281230031", "нет", "500", "оплачено"}) danaFile := prodoctorovPhoneBook(t, "«Дана Медикал» на Ляхова", "запись на приём (телефон) - сентября 2026 ", []string{"21", "10.09.2026 12:30", "79281230021", "нет", "500", "оплачено"}) opts, err := prodoctorovUploadOptsFor(och, [][]byte{ochFile}, from, to) if err != nil { t.Fatalf("выгрузка «Очевидно» не принята в отчёт «Очевидно»: %v", err) } if got := opts.pbxLists["продокторов"]; !reflect.DeepEqual(got, []string{"9281230031"}) { t.Fatalf("телефоны: %v", got) } if _, err := prodoctorovUploadOptsFor(och, [][]byte{danaFile}, from, to); err == nil || !strings.Contains(err.Error(), "другой клиники") || !strings.Contains(err.Error(), "«Очевидно»") { t.Fatalf("файл DANA принят в отчёт «Очевидно»: %v", err) } } func TestPBXEnvHint(t *testing.T) { if got := pbxEnvHint(clinicDANA); got != "MEGAFON_API_URL и MEGAFON_API_KEY" { t.Fatal(got) } if got := pbxEnvHint("ochevidno"); got != "OCHEVIDNO_MEGAFON_API_URL и OCHEVIDNO_MEGAFON_API_KEY" { t.Fatal(got) } } // Без токена или с коротким токеном ссылка отклоняется сразу, до обращения к базе. func TestShareRejectsMissingToken(t *testing.T) { a := &API{} for _, h := range []string{"", "Bearer ", "Bearer short", "Bearer " + strings.Repeat("x", 100)} { req := httptest.NewRequest(http.MethodGet, "/api/v1/share/weekly-reports", nil) if h != "" { req.Header.Set("Authorization", h) } rec := httptest.NewRecorder() a.shareWeeklyReports(rec, req) if rec.Code != http.StatusUnauthorized { t.Fatalf("%q: код %d, ожидали 401", h, rec.Code) } if rec.Header().Get("Cache-Control") != "no-store" { t.Fatalf("%q: ответ можно кешировать", h) } } if shareTokenHash("a") == shareTokenHash("b") || len(shareTokenHash("a")) != 64 { t.Fatal("хеш токена") } } // Блок «Колл-ц» дашборда — совпадения колл-центра с 1С, а не вся выгрузка 1С (как в «Показателях»). func TestWeeklyCallCenterUsesMatchedCallCenter(t *testing.T) { sum := channelsSummary{ Totals: channelsTotals{UniqueFIO: 1021, Services: 3162, Revenue: 7997520}, Channels: []channelRow{ {Name: "Сайт", Patients: 25, Services: 33, Revenue: 215000}, {Name: knownChannels["все номера"].title, Patients: 452, Services: 1890, Revenue: 3441100, AvgCheck: 7613.05}, }, } cc := weeklyCallCenterSales(sum) if cc.Patients != 452 || cc.Services != 1890 || cc.Revenue != 3441100 || cc.AvgCheck != 7613.05 { t.Fatalf("колл-центр взят не из совпадений: %+v", cc) } if got := weeklyCallCenterSales(channelsSummary{Totals: sum.Totals}); got.Revenue != 0 || got.Patients != 0 { t.Fatalf("без листа колл-центра подставились итоги 1С: %+v", got) } }