149 lines
6.9 KiB
Go
149 lines
6.9 KiB
Go
package api
|
||
|
||
import (
|
||
"bytes"
|
||
"context"
|
||
"fmt"
|
||
"io"
|
||
"net/http"
|
||
"os"
|
||
"reflect"
|
||
"strings"
|
||
"testing"
|
||
"time"
|
||
|
||
"dana/internal/sberhealth"
|
||
)
|
||
|
||
func sberTestRows(records ...[]string) [][]string {
|
||
rows := [][]string{{"СберЗдоровье"}, {}, {}, {}, {}, {},
|
||
{"#", "Дата создания записи", "Дата приема", "Пациент", "Телефон", "Врач", "Специальность", "Предоплата", "Сумма предоплаты", "Статус", "Филиал", "Источник", "Причина отказа"}}
|
||
rows = append(rows, records...)
|
||
return append(rows, nil, []string{fmt.Sprintf("Количество элементов: %d", len(records))}, []string{"Дата формирования отчета: 05.10.2026 12:37"})
|
||
}
|
||
|
||
func TestSberHealthCreationDateDedupAndFooter(t *testing.T) {
|
||
from := time.Date(2026, 9, 28, 0, 0, 0, 0, moscow)
|
||
to := time.Date(2026, 10, 6, 0, 0, 0, 0, moscow)
|
||
phones, records, total, err := parseSberHealthRows(sberTestRows(
|
||
[]string{"1", "27.09.2026 23:59", "28.09.2026 12:00", "Тест", "79281230001"},
|
||
[]string{"2", "28.09.2026 00:00", "25.10.2026 12:00", "Тест", "+7 (928) 123-00-02¶"},
|
||
[]string{"3", "05.10.2026 23:59", "", "Тест", "89281230002"},
|
||
[]string{"4", "06.10.2026 00:00", "", "Тест", "79281230003"},
|
||
), from, to)
|
||
if err != nil || records != 2 || total != 4 || !reflect.DeepEqual(phones, []string{"9281230002"}) {
|
||
t.Fatal("creation filter, inclusive last day, dedup or footer incorrect", err, records, total)
|
||
}
|
||
empty, count, total, err := parseSberHealthRows(sberTestRows(), from, to)
|
||
if err != nil || len(empty) != 0 || count != 0 || total != 0 {
|
||
t.Fatal("valid empty report rejected", err)
|
||
}
|
||
}
|
||
|
||
func TestSberHealthInvalidAndIncompleteExports(t *testing.T) {
|
||
from := time.Date(2026, 10, 1, 0, 0, 0, 0, moscow)
|
||
to := from.AddDate(0, 1, 0)
|
||
valid := []string{"1", "05.10.2026 12:30", "", "Тест", "79281230001"}
|
||
truncated := sberTestRows(valid)
|
||
truncated[len(truncated)-2][0] = "Количество элементов: 20"
|
||
missingFooter := sberTestRows(valid)[:8]
|
||
duplicate := sberTestRows(valid, valid)
|
||
badPhone := sberTestRows([]string{"1", "05.10.2026 12:30", "", "Тест", "private-invalid-phone"})
|
||
badDate := sberTestRows([]string{"1", "private-invalid-date", "", "Тест", "79281230001"})
|
||
for _, rows := range [][][]string{truncated, missingFooter, duplicate, badPhone, badDate, {{"unknown format"}}} {
|
||
phones, _, _, err := parseSberHealthRows(rows, from, to)
|
||
if err == nil || len(phones) != 0 || strings.Contains(err.Error(), "private-invalid") {
|
||
t.Fatal("bad/truncated export accepted or private data leaked")
|
||
}
|
||
}
|
||
}
|
||
|
||
func TestSberHealthMergeUsesOnly1CRevenue(t *testing.T) {
|
||
from := time.Date(2026, 10, 1, 0, 0, 0, 0, moscow)
|
||
phones, records, total, err := parseSberHealthRows(sberTestRows(
|
||
[]string{"1", "05.10.2026 12:30", "", "Тест", "79281230001", "", "", "Да", "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 := &sberhealthSourceInfo{Records: records, Phones: len(phones), ExportRecords: total}
|
||
_, result, err := (&API{}).phoneMatchOpts(context.Background(), &progressTracker{}, "book.xlsx", book, matchOpts{pbxLists: lists, sberhealthInfo: info})
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
summary := result.(channelsSummary)
|
||
channel := channelByName(summary, "Сберздоровье")
|
||
if channel.Leads != 2 || channel.Patients != 2 || channel.Revenue != 3500 || channel.Services != 3 || summary.SberHealth != info {
|
||
t.Fatal("merge or 1C-only accounting incorrect")
|
||
}
|
||
}
|
||
|
||
// Opt-in local regression against the user's original sample. Never copied to
|
||
// testdata/repository, never printed, and no external requests are performed.
|
||
func TestSberHealthSuppliedXLSSample(t *testing.T) {
|
||
path := os.Getenv("DANA_SBER_SAMPLE_FILE")
|
||
if path == "" {
|
||
t.Skip("local source sample not supplied")
|
||
}
|
||
data, err := os.ReadFile(path)
|
||
if err != nil {
|
||
t.Fatal("cannot read local sample")
|
||
}
|
||
rows, err := sberhealth.ReadXLS(data)
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
from := time.Date(2026, 9, 28, 0, 0, 0, 0, moscow)
|
||
to := time.Date(2026, 10, 6, 0, 0, 0, 0, moscow)
|
||
phones, records, total, err := parseSberHealthRows(rows, from, to)
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if len(phones) != 6 || records != 7 || total != 7 {
|
||
t.Fatal("sample does not reconcile with independent XLS inspection")
|
||
}
|
||
// Exercise the real integration on local bytes through a fake transport,
|
||
// verifying the exclusive 1C end becomes an inclusive provider date.
|
||
previous := http.DefaultTransport
|
||
defer func() { http.DefaultTransport = previous }()
|
||
http.DefaultTransport = prodoctorovTestTransport(func(r *http.Request) (*http.Response, error) {
|
||
q := r.URL.Query()
|
||
if r.URL.Host != "docdoc.ru" || q.Get("clinic_id") != "294888" || q.Get("crDateFrom") != "28.09.2026" || q.Get("crDateTill") != "05.10.2026" || q.Get("periodInput") != "week" {
|
||
t.Fatal("incorrect inclusive provider date mapping")
|
||
}
|
||
return &http.Response{StatusCode: 200, Header: make(http.Header), Body: io.NopCloser(bytes.NewReader(data))}, nil
|
||
})
|
||
c, _ := sberhealth.New("session=synthetic", "294888")
|
||
opts, err := (&API{sberhealth: c}).sberhealthMatchOpts(context.Background(), from, to)
|
||
if err != nil || opts.sberhealthInfo == nil || opts.sberhealthInfo.Records != 7 || opts.sberhealthInfo.Phones != 6 || opts.sberhealthInfo.ExportRecords != 7 {
|
||
t.Fatal("sample integration failed", err)
|
||
}
|
||
}
|
||
|
||
func TestSberHealthMonthlyRequestAndNoPartialDataOnFailure(t *testing.T) {
|
||
previous := http.DefaultTransport
|
||
defer func() { http.DefaultTransport = previous }()
|
||
http.DefaultTransport = prodoctorovTestTransport(func(r *http.Request) (*http.Response, error) {
|
||
q := r.URL.Query()
|
||
if q.Get("periodInput") != "month" || q.Get("crDateFrom") != "01.10.2026" || q.Get("crDateTill") != "31.10.2026" {
|
||
t.Fatal("monthly date conversion failed")
|
||
}
|
||
return &http.Response{StatusCode: 403, Header: make(http.Header), Body: http.NoBody}, nil
|
||
})
|
||
c, _ := sberhealth.New("session=synthetic", "294888")
|
||
from := time.Date(2026, 10, 1, 0, 0, 0, 0, moscow)
|
||
opts, err := (&API{sberhealth: c}).sberhealthMatchOpts(context.Background(), from, from.AddDate(0, 1, 0))
|
||
if err == nil || opts.sberhealthInfo != nil || len(opts.pbxLists) != 0 {
|
||
t.Fatal("failed download returned successful empty/partial options")
|
||
}
|
||
}
|