182 lines
9.3 KiB
Go
182 lines
9.3 KiB
Go
package api
|
||
|
||
import (
|
||
"bytes"
|
||
"mime/multipart"
|
||
"net/http"
|
||
"net/http/httptest"
|
||
"reflect"
|
||
"strings"
|
||
"testing"
|
||
"time"
|
||
)
|
||
|
||
// Книга ПроДокторова как в кабинете: во второй строке месяц выгрузки («… - октября 2026»).
|
||
func prodoctorovMonthBook(t *testing.T, monthLine string, records ...[]string) []byte {
|
||
t.Helper()
|
||
rows := [][]string{{"«Дана Медикал» тестовый филиал"}, {monthLine}, {},
|
||
{"№", "Дата", "Пациент", "Информация", "Специальность", "Запись на", "Цена приёма, р.", "Сумма, р.", "Оплата"}}
|
||
return xlsxBook(t, map[string][][]string{"Sheet": append(rows, records...)}, []string{"Sheet"})
|
||
}
|
||
|
||
// Телефонная выгрузка ПроДокторова: вместо «Информация … Запись на» — «Экспресс-запись».
|
||
func prodoctorovPhoneBook(t *testing.T, clinic, monthLine string, records ...[]string) []byte {
|
||
t.Helper()
|
||
rows := [][]string{{clinic}, {monthLine}, {},
|
||
{"№", "Дата", "Пациент", "Экспресс-запись", "Сумма, р.", "Оплата"}}
|
||
return xlsxBook(t, map[string][][]string{"Sheet": append(rows, records...)}, []string{"Sheet"})
|
||
}
|
||
|
||
func TestProdoctorovPhoneExportAndBothKindsPerMonth(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)
|
||
online := prodoctorovMonthBook(t, "запись на приём (в МИС) - сентября 2026 ",
|
||
[]string{"11", "03.09.2026 10:00", "79281230011", "", "", "05.09.2026 10:00"},
|
||
[]string{"12", "04.09.2026 10:00", "79281230099", "", "", "06.09.2026 10:00"},
|
||
)
|
||
phone := prodoctorovPhoneBook(t, "«Дана Медикал» на Ляхова", "запись на приём (телефон) - сентября 2026 ",
|
||
[]string{"21", "10.09.2026 12:30", "79281230021", "нет", "500", "оплачено"},
|
||
[]string{"22", "11.09.2026 12:30", "79281230099", "нет", "500", "оплачено"}, // тот же номер, что в онлайн-записи
|
||
)
|
||
|
||
// одна телефонная выгрузка читается сама по себе
|
||
opts, err := prodoctorovUploadOpts([][]byte{phone}, from, to)
|
||
if err != nil {
|
||
t.Fatalf("телефонная выгрузка не принята: %v", err)
|
||
}
|
||
if got := opts.pbxLists["продокторов"]; !reflect.DeepEqual(got, []string{"9281230021", "9281230099"}) {
|
||
t.Fatalf("телефоны из телефонной выгрузки: %v", got)
|
||
}
|
||
|
||
// онлайн и телефон за один месяц — можно, номера объединяются без дублей
|
||
opts, err = prodoctorovUploadOpts([][]byte{online, phone}, from, to)
|
||
if err != nil {
|
||
t.Fatalf("оба вида за месяц не приняты: %v", err)
|
||
}
|
||
want := []string{"9281230011", "9281230021", "9281230099"}
|
||
if got := opts.pbxLists["продокторов"]; !reflect.DeepEqual(got, want) {
|
||
t.Fatalf("телефоны %v, ожидали %v", got, want)
|
||
}
|
||
if i := opts.prodoctorovInfo; i.Months != 1 || i.Records != 4 || i.Phones != 3 {
|
||
t.Fatalf("сводка: %+v", i)
|
||
}
|
||
|
||
// две телефонные выгрузки за один месяц — по-прежнему дубль
|
||
if _, err := prodoctorovUploadOpts([][]byte{phone, phone}, from, to); err == nil || !strings.Contains(err.Error(), "телефонная запись") {
|
||
t.Fatalf("дубль телефонной выгрузки принят: %v", err)
|
||
}
|
||
|
||
// выгрузка другой клиники (у «Очевидно» такие же файлы) не должна попасть в отчёт DANA
|
||
other := prodoctorovPhoneBook(t, "Офтальмологический центр «Очевидно»", "запись на приём (телефон) - сентября 2026 ",
|
||
[]string{"31", "10.09.2026 12:30", "79281230031", "нет", "500", "оплачено"})
|
||
if _, err := prodoctorovUploadOpts([][]byte{other}, from, to); err == nil || !strings.Contains(err.Error(), "другой клиники") {
|
||
t.Fatalf("файл чужой клиники принят: %v", err)
|
||
}
|
||
}
|
||
|
||
func TestProdoctorovUploadCoversEveryMonthOfPeriod(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) // неделя на стыке сентября и октября
|
||
sep := prodoctorovMonthBook(t, "запись на приём (в МИС) - сентября 2026 ",
|
||
[]string{"1", "27.09.2026 10:00", "79281230001", "", "", "30.09.2026 10:00"}, // до периода
|
||
[]string{"2", "29.09.2026 10:00", "79281230002", "", "", "01.10.2026 10:00"},
|
||
)
|
||
oct := prodoctorovMonthBook(t, "запись на приём (в МИС) - октября 2026 ",
|
||
[]string{"3", "02.10.2026 10:00", "+7 (928) 123-00-03", "", "", "03.10.2026 10:00"},
|
||
[]string{"4", "06.10.2026 00:00", "79281230004", "", "", "07.10.2026 10:00"}, // правая граница исключается
|
||
)
|
||
|
||
// Только октябрь: сентябрь нужен периоду, молча терять его записи нельзя.
|
||
if _, err := prodoctorovUploadOpts([][]byte{oct}, from, to); err == nil || !strings.Contains(err.Error(), "сентябрь 2026") {
|
||
t.Fatalf("пропущенный месяц не замечен: %v", err)
|
||
}
|
||
opts, err := prodoctorovUploadOpts([][]byte{oct, sep}, from, to)
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
want := []string{"9281230002", "9281230003"}
|
||
if got := opts.pbxLists["продокторов"]; !reflect.DeepEqual(got, want) {
|
||
t.Fatalf("телефоны %v, ожидали %v", got, want)
|
||
}
|
||
if i := opts.prodoctorovInfo; i == nil || i.Months != 2 || i.Records != 2 || i.Phones != 2 {
|
||
t.Fatalf("сводка по источнику неверна: %+v", i)
|
||
}
|
||
|
||
nov := prodoctorovMonthBook(t, "запись на приём (в МИС) - ноября 2026 ")
|
||
if _, err := prodoctorovUploadOpts([][]byte{oct, sep, nov}, from, to); err == nil || !strings.Contains(err.Error(), "не относится") {
|
||
t.Fatalf("лишний месяц принят: %v", err)
|
||
}
|
||
if _, err := prodoctorovUploadOpts([][]byte{oct, oct, sep}, from, to); err == nil || !strings.Contains(err.Error(), "два файла") {
|
||
t.Fatalf("дубль месяца принят: %v", err)
|
||
}
|
||
noMonth := xlsxBook(t, map[string][][]string{"Sheet": {{"«Дана Медикал»"}, {"без месяца"}}}, []string{"Sheet"})
|
||
if _, err := prodoctorovUploadOpts([][]byte{noMonth}, from, to); err == nil {
|
||
t.Fatal("файл без месяца принят")
|
||
}
|
||
}
|
||
|
||
func TestProdoctorovUploadValidEmptyMonthStaysExplicitChannel(t *testing.T) {
|
||
from := time.Date(2026, 10, 1, 0, 0, 0, 0, moscow)
|
||
to := time.Date(2026, 11, 1, 0, 0, 0, 0, moscow)
|
||
opts, err := prodoctorovUploadOpts([][]byte{prodoctorovMonthBook(t, "запись на приём (в МИС) - октября 2026 ")}, from, to)
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if got, ok := opts.pbxLists["продокторов"]; !ok || len(got) != 0 || opts.prodoctorovInfo.Records != 0 {
|
||
t.Fatal("пустой месяц должен давать явный пустой канал, а не «не подключено»")
|
||
}
|
||
}
|
||
|
||
func multipartRequest(t *testing.T, parts map[string][]struct{ name, body string }) *http.Request {
|
||
t.Helper()
|
||
var buf bytes.Buffer
|
||
w := multipart.NewWriter(&buf)
|
||
for field, files := range parts {
|
||
for _, f := range files {
|
||
fw, err := w.CreateFormFile(field, f.name)
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
_, _ = fw.Write([]byte(f.body))
|
||
}
|
||
}
|
||
_ = w.Close()
|
||
r := httptest.NewRequest(http.MethodPost, "/api/v1/admin/reports", &buf)
|
||
r.Header.Set("Content-Type", w.FormDataContentType())
|
||
if err := r.ParseMultipartForm(1 << 20); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
return r
|
||
}
|
||
|
||
type part = struct{ name, body string }
|
||
|
||
func TestReadPartnerUploadsValidation(t *testing.T) {
|
||
ole := string(oleSignature) + strings.Repeat("x", 600)
|
||
zip := string(zipSignature) + strings.Repeat("x", 600)
|
||
|
||
up, err := readPartnerUploads(multipartRequest(t, map[string][]part{
|
||
"sber_file": {{"Report_20261005_171611.xls", ole}},
|
||
"prodoctorov_file": {{"Appointment-2026-9.xlsx", zip}, {"Appointment-2026-10.xlsx", zip}},
|
||
}))
|
||
if err != nil || len(up.sber) == 0 || len(up.prodoctorov) != 2 || !up.any() {
|
||
t.Fatalf("корректные файлы отклонены: %v", err)
|
||
}
|
||
if up, err := readPartnerUploads(multipartRequest(t, nil)); err != nil || up.any() {
|
||
t.Fatalf("без файлов должно быть пусто: %v", err)
|
||
}
|
||
|
||
for name, parts := range map[string]map[string][]part{
|
||
"Сбер не xls": {"sber_file": {{"report.xlsx", ole}}},
|
||
"Сбер вместо Excel HTML": {"sber_file": {{"report.xls", "<html>login</html>" + strings.Repeat(" ", 600)}}},
|
||
"Сбер два файла": {"sber_file": {{"a.xls", ole}, {"b.xls", ole}}},
|
||
"ПД не xlsx": {"prodoctorov_file": {{"a.xls", zip}}},
|
||
"ПД вместо Excel HTML": {"prodoctorov_file": {{"a.xlsx", "<html>403</html>" + strings.Repeat(" ", 600)}}},
|
||
"ПД семь файлов": {"prodoctorov_file": {{"1.xlsx", zip}, {"2.xlsx", zip}, {"3.xlsx", zip}, {"4.xlsx", zip}, {"5.xlsx", zip}, {"6.xlsx", zip}, {"7.xlsx", zip}}},
|
||
} {
|
||
if _, err := readPartnerUploads(multipartRequest(t, parts)); err == nil {
|
||
t.Errorf("%s: файл принят", name)
|
||
}
|
||
}
|
||
}
|