cambios propuestos por dru
This commit is contained in:
+177
@@ -6,7 +6,9 @@ import (
|
||||
"net/http/httptest"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// La allowlist del proxy debe ser SOLO de lectura: cualquier escritura o búsqueda
|
||||
@@ -139,6 +141,82 @@ func TestHandleBook(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// El teléfono identifica al cliente: si ya existe en EA se reutiliza (no se
|
||||
// duplica) y se le pueden crear varias citas. El email ya no es obligatorio.
|
||||
func TestHandleBookReusesCustomerByPhone(t *testing.T) {
|
||||
customersCreated := 0
|
||||
apptsCreated := 0
|
||||
var lastAppt eaAppointment
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/index.php/api/v1/services", func(w http.ResponseWriter, r *http.Request) {
|
||||
json.NewEncoder(w).Encode([]eaService{{ID: 1, Name: "Corte", Duration: 30}})
|
||||
})
|
||||
mux.HandleFunc("/index.php/api/v1/providers", func(w http.ResponseWriter, r *http.Request) {
|
||||
json.NewEncoder(w).Encode([]eaProvider{{ID: 7}})
|
||||
})
|
||||
mux.HandleFunc("/index.php/api/v1/customers", func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == http.MethodGet {
|
||||
if q := r.URL.Query().Get("q"); q != "690918815" {
|
||||
t.Errorf("q = %q, want los últimos 9 dígitos del teléfono", q)
|
||||
}
|
||||
// En EA está guardado con prefijo y espacios: debe reconocerse igual.
|
||||
json.NewEncoder(w).Encode([]eaCustomer{{ID: 42, Phone: "+34 690 91 88 15"}})
|
||||
return
|
||||
}
|
||||
customersCreated++
|
||||
json.NewEncoder(w).Encode(eaCustomer{ID: 99})
|
||||
})
|
||||
mux.HandleFunc("/index.php/api/v1/appointments", func(w http.ResponseWriter, r *http.Request) {
|
||||
apptsCreated++
|
||||
json.NewDecoder(r.Body).Decode(&lastAppt)
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
})
|
||||
srv := httptest.NewServer(mux)
|
||||
defer srv.Close()
|
||||
ea := &eaClient{base: srv.URL, http: srv.Client()}
|
||||
|
||||
// Dos reservas sin email y con el teléfono escrito distinto cada vez.
|
||||
for _, tel := range []string{"690 918 815", "+34690918815"} {
|
||||
body := `{"nombre":"Ana","apellidos":"López","telefono":"` + tel + `","serviceIds":[1],"date":"2026-07-01","time":"10:00"}`
|
||||
rec := httptest.NewRecorder()
|
||||
handleBook(ea)(rec, httptest.NewRequest(http.MethodPost, "/api/book", strings.NewReader(body)))
|
||||
if rec.Code != http.StatusCreated {
|
||||
t.Fatalf("status = %d, want 201; body=%s", rec.Code, rec.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
if customersCreated != 0 {
|
||||
t.Errorf("se crearon %d clientes nuevos; ese teléfono ya existía en EA", customersCreated)
|
||||
}
|
||||
if apptsCreated != 2 {
|
||||
t.Errorf("citas creadas = %d, want 2 (varias citas para el mismo cliente)", apptsCreated)
|
||||
}
|
||||
if lastAppt.CustomerID != 42 {
|
||||
t.Errorf("customerId = %d, want 42 (el cliente existente)", lastAppt.CustomerID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSamePhone(t *testing.T) {
|
||||
cases := []struct {
|
||||
a, b string
|
||||
want bool
|
||||
}{
|
||||
{"690918815", "690 91 88 15", true},
|
||||
{"+34 690918815", "690918815", true},
|
||||
{"0034690918815", "690.91.88.15", true},
|
||||
{"690918815", "690918816", false},
|
||||
{"600", "600", true},
|
||||
{"600", "222600", false}, // sufijos cortos no identifican
|
||||
{"", "690918815", false},
|
||||
}
|
||||
for _, c := range cases {
|
||||
if got := samePhone(c.a, c.b); got != c.want {
|
||||
t.Errorf("samePhone(%q, %q) = %v, want %v", c.a, c.b, got, c.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Servicio inválido (no existe en EA) ⇒ 400 y NO se crea ninguna cita.
|
||||
func TestHandleBookRejectsUnknownService(t *testing.T) {
|
||||
created := false
|
||||
@@ -230,6 +308,82 @@ func TestProviderID(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
// /api/month-availability agrega las availabilities de EA día a día: devuelve
|
||||
// los días sin ningún hueco para que el calendario los deshabilite, y cachea
|
||||
// el resultado (una sola pasada de llamadas a EA por mes+servicio).
|
||||
func TestHandleMonthAvailability(t *testing.T) {
|
||||
// Mes siguiente al actual: así ningún día es pasado y se consultan todos.
|
||||
now := time.Now()
|
||||
first := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, time.UTC).AddDate(0, 1, 0)
|
||||
month := first.Format("2006-01")
|
||||
daysInMonth := first.AddDate(0, 1, -1).Day()
|
||||
full1, full2 := month+"-15", month+"-16"
|
||||
|
||||
var eaCalls atomic.Int64
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/index.php/api/v1/providers", func(w http.ResponseWriter, r *http.Request) {
|
||||
json.NewEncoder(w).Encode([]eaProvider{{ID: 7}})
|
||||
})
|
||||
mux.HandleFunc("/index.php/api/v1/availabilities", func(w http.ResponseWriter, r *http.Request) {
|
||||
eaCalls.Add(1)
|
||||
if r.URL.Query().Get("serviceId") != "3" || r.URL.Query().Get("providerId") != "7" {
|
||||
t.Errorf("query inesperada: %s", r.URL.RawQuery)
|
||||
}
|
||||
if date := r.URL.Query().Get("date"); date == full1 || date == full2 {
|
||||
w.Write([]byte(`[]`)) // día completo: sin ningún hueco
|
||||
return
|
||||
}
|
||||
w.Write([]byte(`["10:00","10:30"]`))
|
||||
})
|
||||
srv := httptest.NewServer(mux)
|
||||
defer srv.Close()
|
||||
handler := handleMonthAvailability(&eaClient{base: srv.URL, http: srv.Client()})
|
||||
|
||||
get := func() *httptest.ResponseRecorder {
|
||||
rec := httptest.NewRecorder()
|
||||
handler(rec, httptest.NewRequest(http.MethodGet, "/api/month-availability?month="+month+"&serviceId=3", nil))
|
||||
return rec
|
||||
}
|
||||
|
||||
rec := get()
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("status = %d, want 200; body=%s", rec.Code, rec.Body.String())
|
||||
}
|
||||
var got struct {
|
||||
UnavailableDays []string `json:"unavailableDays"`
|
||||
}
|
||||
if err := json.Unmarshal(rec.Body.Bytes(), &got); err != nil {
|
||||
t.Fatalf("json: %v", err)
|
||||
}
|
||||
if strings.Join(got.UnavailableDays, ",") != full1+","+full2 {
|
||||
t.Errorf("unavailableDays = %v, want [%s %s]", got.UnavailableDays, full1, full2)
|
||||
}
|
||||
if n := eaCalls.Load(); n != int64(daysInMonth) {
|
||||
t.Errorf("llamadas a EA = %d, want %d (una por día del mes)", n, daysInMonth)
|
||||
}
|
||||
|
||||
// Segunda petición: servida desde la caché, sin más llamadas a EA.
|
||||
if rec := get(); rec.Code != http.StatusOK {
|
||||
t.Fatalf("status con caché = %d, want 200", rec.Code)
|
||||
}
|
||||
if n := eaCalls.Load(); n != int64(daysInMonth) {
|
||||
t.Errorf("la caché no se usó: %d llamadas a EA", n)
|
||||
}
|
||||
|
||||
// Parámetros inválidos ⇒ 400 (meses pasados, sin servicio, formato malo).
|
||||
for _, qs := range []string{
|
||||
"month=2020-01&serviceId=3",
|
||||
"month=" + month,
|
||||
"month=13-2026&serviceId=3",
|
||||
} {
|
||||
rec := httptest.NewRecorder()
|
||||
handler(rec, httptest.NewRequest(http.MethodGet, "/api/month-availability?"+qs, nil))
|
||||
if rec.Code != http.StatusBadRequest {
|
||||
t.Errorf("status = %d, want 400 (%s)", rec.Code, qs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sin apellidos ⇒ 400 sin llegar a llamar a EA.
|
||||
func TestHandleBookRequiresApellidos(t *testing.T) {
|
||||
called := false
|
||||
@@ -250,3 +404,26 @@ func TestHandleBookRequiresApellidos(t *testing.T) {
|
||||
t.Error("se llamó a EA con datos incompletos")
|
||||
}
|
||||
}
|
||||
|
||||
// Sin teléfono (o sin ningún dígito) ⇒ 400 sin llamar a EA: es el dato que
|
||||
// identifica al cliente. El email en cambio ya no es obligatorio.
|
||||
func TestHandleBookRequiresTelefono(t *testing.T) {
|
||||
called := false
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { called = true }))
|
||||
defer srv.Close()
|
||||
ea := &eaClient{base: srv.URL, http: srv.Client()}
|
||||
|
||||
for _, body := range []string{
|
||||
`{"nombre":"Ana","apellidos":"López","serviceIds":[1],"date":"2026-07-01","time":"10:00"}`,
|
||||
`{"nombre":"Ana","apellidos":"López","telefono":"--","serviceIds":[1],"date":"2026-07-01","time":"10:00"}`,
|
||||
} {
|
||||
rec := httptest.NewRecorder()
|
||||
handleBook(ea)(rec, httptest.NewRequest(http.MethodPost, "/api/book", strings.NewReader(body)))
|
||||
if rec.Code != http.StatusBadRequest {
|
||||
t.Errorf("status = %d, want 400 (body %s)", rec.Code, body)
|
||||
}
|
||||
}
|
||||
if called {
|
||||
t.Error("se llamó a EA con datos incompletos")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user