121 lines
4.1 KiB
Go
121 lines
4.1 KiB
Go
package api
|
||
|
||
import (
|
||
"bytes"
|
||
"context"
|
||
"encoding/json"
|
||
"fmt"
|
||
"net/http/httptest"
|
||
"os"
|
||
"slices"
|
||
"testing"
|
||
"time"
|
||
|
||
"dana/internal/onec"
|
||
"dana/internal/storage"
|
||
)
|
||
|
||
// Uses only a disposable database; no real 1C calls or patient records.
|
||
func TestAutonomousAllActualFutureDates(t *testing.T) {
|
||
dsn := os.Getenv("DANA_AUTONOMY_TEST_DSN")
|
||
if dsn == "" {
|
||
t.Skip("requires disposable autonomy DB")
|
||
}
|
||
ctx := context.Background()
|
||
st, err := storage.New(ctx, dsn)
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
defer st.Close()
|
||
if err = st.Migrate(ctx); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
app := &API{store: st, limiter: newRateLimiter(), onecHook: "all-dates-test"}
|
||
mux := app.Routes()
|
||
call := func(method, path string, body any, want int) *httptest.ResponseRecorder {
|
||
t.Helper()
|
||
b, _ := json.Marshal(body)
|
||
r := httptest.NewRequest(method, path, bytes.NewReader(b))
|
||
r.Header.Set("X-Api-Key", "all-dates-test")
|
||
w := httptest.NewRecorder()
|
||
mux.ServeHTTP(w, r)
|
||
if w.Code != want {
|
||
t.Fatalf("%s %s: %d, want %d: %s", method, path, w.Code, want, w.Body.String())
|
||
}
|
||
return w
|
||
}
|
||
doctor := fmt.Sprintf("all-dates-%d", time.Now().UnixNano())
|
||
var doctorID int64
|
||
if err = st.Pool().QueryRow(ctx, `INSERT INTO doctors(slug,last_name,first_name,booking_provider) VALUES($1,'Тест','Все даты','native') RETURNING id`, doctor).Scan(&doctorID); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if _, err = st.Pool().Exec(ctx, `INSERT INTO onec_doctors(onec_id,full_name,doctor_id) VALUES($1,'Тест Все даты',$2)`, doctor, doctorID); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if err = st.SetBookingSettings(ctx, storage.BookingSettings{}); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
from := time.Now().UTC().Truncate(time.Hour)
|
||
far := from.AddDate(0, 0, 90)
|
||
end := far.Add(30 * time.Minute)
|
||
to := end.Add(time.Hour)
|
||
slot := onec.Slot{DoctorID: doctor, BranchID: "branch", Start: far, End: end, Status: "free"}
|
||
payload := onecSyncPayload{From: &from, To: &to, Branches: []onec.Branch{}, Doctors: []onec.Doctor{}, Services: []onecDoctorServices{}, Slots: []onec.Slot{slot}}
|
||
call("POST", "/api/v1/onec/sync/all", payload, 200)
|
||
shown := call("GET", "/api/v1/doctors/"+doctor+"/slots", nil, 200)
|
||
var result struct {
|
||
Data struct {
|
||
Enabled bool `json:"enabled"`
|
||
Slots []storage.OnecSlot `json:"slots"`
|
||
} `json:"data"`
|
||
}
|
||
if err = json.Unmarshal(shown.Body.Bytes(), &result); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if !result.Data.Enabled || len(result.Data.Slots) != 1 || !result.Data.Slots[0].Start.Equal(far) {
|
||
t.Fatalf("actual distant slot hidden: %s", shown.Body.String())
|
||
}
|
||
checkAvailability := func(want bool) {
|
||
t.Helper()
|
||
w := call("GET", "/api/v1/booking/availability", nil, 200)
|
||
var availability struct {
|
||
Data struct {
|
||
Enabled bool `json:"enabled"`
|
||
Days []storage.BookingDay `json:"days"`
|
||
} `json:"data"`
|
||
}
|
||
if err := json.Unmarshal(w.Body.Bytes(), &availability); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
found := false
|
||
for _, day := range availability.Data.Days {
|
||
if day.Date == far.In(moscow).Format("2006-01-02") && slices.Contains(day.DoctorIDs, doctorID) {
|
||
found = true
|
||
}
|
||
}
|
||
if !availability.Data.Enabled || found != want {
|
||
t.Fatalf("date-to-doctor availability mismatch: %s", w.Body.String())
|
||
}
|
||
}
|
||
checkAvailability(true)
|
||
creation := appointmentInput{DoctorSlug: doctor, Start: far.Format(time.RFC3339), Name: "Test", Phone: "79990000001", Consent: true, RequestID: doctor}
|
||
made := call("POST", "/api/v1/appointments", creation, 201)
|
||
if !bytes.Contains(made.Body.Bytes(), []byte(`"mode":"pending"`)) {
|
||
t.Fatal("distant actual slot not queued")
|
||
}
|
||
// A push claiming free must not reopen the pending reservation.
|
||
call("POST", "/api/v1/onec/sync/all", payload, 200)
|
||
hidden := call("GET", "/api/v1/doctors/"+doctor+"/slots", nil, 200)
|
||
if err = json.Unmarshal(hidden.Body.Bytes(), &result); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if len(result.Data.Slots) != 0 {
|
||
t.Fatal("reserved distant slot reopened")
|
||
}
|
||
checkAvailability(false)
|
||
// Removing the calendar cutoff must not permit invented dates.
|
||
creation.Start = far.Add(time.Hour).Format(time.RFC3339)
|
||
creation.RequestID = doctor + "-invented"
|
||
call("POST", "/api/v1/appointments", creation, 409)
|
||
}
|