dana-medical-site/internal/api/report_prodoctorov_test.go
2026-10-14 13:33:13 +00:00

140 lines
6.5 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package api
import (
"bytes"
"context"
"io"
"net/http"
"reflect"
"strings"
"testing"
"time"
"dana/internal/prodoctorov"
)
func prodoctorovTestBook(t *testing.T, records ...[]string) []byte {
t.Helper()
rows := [][]string{{"Онлайн-записи"}, {"Синтетическая клиника"}, {},
{"№", "Дата", "Пациент", "Информация", "Специальность", "Запись на", "Цена приёма, р.", "Сумма, р.", "Оплата"}}
return xlsxBook(t, map[string][][]string{"Sheet": append(rows, records...)}, []string{"Sheet"})
}
func TestProdoctorovCreationDateAndDeduplication(t *testing.T) {
from := time.Date(2026, 10, 5, 0, 0, 0, 0, moscow)
to := from.AddDate(0, 0, 7)
book := prodoctorovTestBook(t,
[]string{"1", "04.10.2026 23:59", "79281230001", "", "", "06.10.2026 12:30"}, // visit in period, creation outside
[]string{"2", "05.10.2026 00:00", "+7 (928) 123-00-02¶", "", "", "25.10.2026 12:30"}, // creation in period, visit outside
[]string{"3", "11.10.2026 23:59", "89281230002", "", "", "25.10.2026 12:30"}, // duplicate phone
[]string{"4", "12.10.2026 00:00", "79281230003", "", "", "12.10.2026 12:30"}, // exclusive end
)
phones, records, err := parseProdoctorovExport(book, from, to)
if err != nil || records != 2 || !reflect.DeepEqual(phones, []string{"9281230002"}) {
t.Fatal("creation date, boundaries or deduplication incorrect", err, records)
}
}
func TestProdoctorovRejectsChangedFormatAndBadRows(t *testing.T) {
from := time.Date(2026, 10, 1, 0, 0, 0, 0, moscow)
to := from.AddDate(0, 1, 0)
for _, book := range [][]byte{
[]byte("<html>login</html>"),
xlsxBook(t, map[string][][]string{"Sheet": {{"wrong format"}}}, []string{"Sheet"}),
prodoctorovTestBook(t, []string{"1", "private-invalid-date", "79281230001"}),
prodoctorovTestBook(t, []string{"1", "05.10.2026 12:30", "private-invalid-phone"}),
} {
_, _, err := parseProdoctorovExport(book, from, to)
if err == nil || strings.Contains(err.Error(), "private-invalid") {
t.Fatal("invalid export accepted or private row leaked")
}
}
phones, records, err := parseProdoctorovExport(prodoctorovTestBook(t), from, to)
if err != nil || len(phones) != 0 || records != 0 {
t.Fatal("valid empty export rejected", err)
}
}
// Route the production fixed-host client to a synthetic transport, never the
// actual cabinet. This also verifies requests use the supplied LPU explicitly.
type prodoctorovTestTransport func(*http.Request) (*http.Response, error)
func (f prodoctorovTestTransport) RoundTrip(r *http.Request) (*http.Response, error) { return f(r) }
func TestProdoctorovOverlappingMonths(t *testing.T) {
previous := http.DefaultTransport
defer func() { http.DefaultTransport = previous }()
var requested []string
// Use XLSX bodies below; no real request/cookie/data ever leaves this test.
http.DefaultTransport = prodoctorovTestTransport(func(r *http.Request) (*http.Response, error) {
if r.URL.Host != "prodoctorov.ru" || r.URL.Query().Get("lpu") != "82347" {
t.Fatal("wrong host or clinic")
}
requested = append(requested, r.URL.Query().Get("year")+"-"+r.URL.Query().Get("month"))
body := prodoctorovTestBook(t)
return &http.Response{StatusCode: 200, Header: make(http.Header), Body: io.NopCloser(bytes.NewReader(body))}, nil
})
c, _ := prodoctorov.New("session=synthetic", "82347")
a := &API{prodoctorov: c}
from := time.Date(2026, 12, 28, 0, 0, 0, 0, moscow)
to := time.Date(2027, 1, 4, 0, 0, 0, 0, moscow)
o, err := a.prodoctorovMatchOpts(context.Background(), from, to)
if err != nil || !reflect.DeepEqual(requested, []string{"2026-12", "2027-1"}) || o.prodoctorovInfo.Months != 2 {
t.Fatal("cross-year month selection incorrect", err, requested)
}
requested = nil
_, err = a.prodoctorovMatchOpts(context.Background(), time.Date(2026, 10, 1, 0, 0, 0, 0, moscow), time.Date(2026, 11, 1, 0, 0, 0, 0, moscow))
if err != nil || !reflect.DeepEqual(requested, []string{"2026-10"}) {
t.Fatal("exclusive month end fetched an extra month", err, requested)
}
}
func TestProdoctorovMergesWithExistingChannelAndUsesOnly1CRevenue(t *testing.T) {
from := time.Date(2026, 10, 1, 0, 0, 0, 0, moscow)
phones, records, err := parseProdoctorovExport(prodoctorovTestBook(t,
[]string{"1", "05.10.2026 12:30", "79281230001", "", "", "06.10.2026 12:30", "999999", "999999"},
[]string{"2", "05.10.2026 12:30", "79281230002"},
), from, from.AddDate(0, 1, 0))
if err != nil {
t.Fatal(err)
}
book := xlsxBook(t, map[string][][]string{
"Лист_1": {{"Пациент", "Мобильный телефон", "Количество", "СуммаИтого"},
{"Тестов Один", "79281230001", "2", "1500"},
{"Другой Два", "79281230002", "1", "2000"},
{"Итого", "", "3", "3500"}},
"Продокторов": {{"79281230001"}},
}, []string{"Лист_1", "Продокторов"})
lists := mergeChannelLists(map[string][]string{"продокторов": {"89281230001"}}, map[string][]string{"продокторов": phones})
info := &prodoctorovSourceInfo{Records: records, Phones: len(phones)}
_, summary, err := (&API{}).phoneMatchOpts(context.Background(), &progressTracker{}, "book.xlsx", book,
matchOpts{pbxLists: lists, prodoctorovInfo: info})
if err != nil {
t.Fatal(err)
}
s := summary.(channelsSummary)
c := channelByName(s, "Продокторов")
if c.Leads != 2 || c.Patients != 2 || c.Services != 3 || c.Revenue != 3500 || s.Prodoctorov != info {
t.Fatalf("merged channel or 1C accounting incorrect: %+v", c)
}
}
func TestProdoctorovFailedSecondMonthDoesNotReturnPartialData(t *testing.T) {
previous := http.DefaultTransport
defer func() { http.DefaultTransport = previous }()
http.DefaultTransport = prodoctorovTestTransport(func(r *http.Request) (*http.Response, error) {
if r.URL.Query().Get("month") == "11" {
return &http.Response{StatusCode: 403, Header: make(http.Header), Body: http.NoBody}, nil
}
body := prodoctorovTestBook(t, []string{"1", "31.10.2026 12:30", "79281230001"})
return &http.Response{StatusCode: 200, Header: make(http.Header), Body: io.NopCloser(bytes.NewReader(body))}, nil
})
c, _ := prodoctorov.New("session=synthetic", "82347")
from := time.Date(2026, 10, 26, 0, 0, 0, 0, moscow)
to := time.Date(2026, 11, 2, 0, 0, 0, 0, moscow)
o, err := (&API{prodoctorov: c}).prodoctorovMatchOpts(context.Background(), from, to)
if err == nil || o.prodoctorovInfo != nil || len(o.pbxLists) != 0 {
t.Fatal("failed monthly export returned partial successful data")
}
}