140 lines
6.5 KiB
Go
140 lines
6.5 KiB
Go
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")
|
||
}
|
||
}
|