revision de cloud code
This commit is contained in:
+168
@@ -0,0 +1,168 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// La allowlist del proxy debe ser SOLO de lectura: cualquier escritura o búsqueda
|
||||
// de clientes se hace server-side en /api/book, nunca por el proxy.
|
||||
func TestEaRouteAllowed(t *testing.T) {
|
||||
cases := []struct {
|
||||
method string
|
||||
path string
|
||||
want bool
|
||||
}{
|
||||
// Lecturas permitidas (las que usa el frontend)
|
||||
{"GET", "/ea-api/v1/categories", true},
|
||||
{"GET", "/ea-api/v1/service_categories", true},
|
||||
{"GET", "/ea-api/v1/services", true},
|
||||
{"GET", "/ea-api/v1/availabilities", true},
|
||||
{"GET", "/ea-api/v1/providers/1", true},
|
||||
|
||||
// Bloqueadas: clientes y citas ya NO pasan por el proxy
|
||||
{"GET", "/ea-api/v1/customers", false},
|
||||
{"POST", "/ea-api/v1/customers", false},
|
||||
{"POST", "/ea-api/v1/appointments", false},
|
||||
{"GET", "/ea-api/v1/appointments", false},
|
||||
{"DELETE", "/ea-api/v1/appointments/3", false},
|
||||
{"PUT", "/ea-api/v1/customers/5", false},
|
||||
{"GET", "/ea-api/v1/providers", false}, // listar todos
|
||||
{"GET", "/ea-api/v1/providers/abc", false}, // id no numérico
|
||||
{"GET", "/ea-api/v1/settings", false},
|
||||
{"POST", "/ea-api/v1/services", false},
|
||||
}
|
||||
for _, c := range cases {
|
||||
if got := eaRouteAllowed(c.method, c.path); got != c.want {
|
||||
t.Errorf("eaRouteAllowed(%q, %q) = %v, want %v", c.method, c.path, got, c.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSplitName(t *testing.T) {
|
||||
cases := []struct{ in, first, last string }{
|
||||
{"", "", ""},
|
||||
{"Ana", "Ana", ""},
|
||||
{"Ana López", "Ana", "López"},
|
||||
{"Ana María López Gil", "Ana", "María López Gil"},
|
||||
{" Ana López ", "Ana", "López"},
|
||||
}
|
||||
for _, c := range cases {
|
||||
f, l := splitName(c.in)
|
||||
if f != c.first || l != c.last {
|
||||
t.Errorf("splitName(%q) = (%q,%q), want (%q,%q)", c.in, f, l, c.first, c.last)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFlexIntUnmarshal(t *testing.T) {
|
||||
var s struct {
|
||||
A flexInt `json:"a"`
|
||||
B flexInt `json:"b"`
|
||||
C flexInt `json:"c"`
|
||||
D flexInt `json:"d"`
|
||||
}
|
||||
if err := json.Unmarshal([]byte(`{"a":30,"b":"45","c":"60.0","d":null}`), &s); err != nil {
|
||||
t.Fatalf("unmarshal: %v", err)
|
||||
}
|
||||
if s.A != 30 || s.B != 45 || s.C != 60 || s.D != 0 {
|
||||
t.Errorf("got %d,%d,%d,%d want 30,45,60,0", s.A, s.B, s.C, s.D)
|
||||
}
|
||||
}
|
||||
|
||||
// TestHandleBook comprueba que una reserva se orquesta ENTERAMENTE contra la API
|
||||
// de EasyAppointments: lee servicios, crea el cliente y crea la cita, calculando
|
||||
// duración total y servicio principal en el servidor.
|
||||
func TestHandleBook(t *testing.T) {
|
||||
var gotAppt eaAppointment
|
||||
var hits []string
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/index.php/api/v1/services", func(w http.ResponseWriter, r *http.Request) {
|
||||
hits = append(hits, "GET services")
|
||||
json.NewEncoder(w).Encode([]eaService{
|
||||
{ID: 1, Name: "Corte", Duration: 30},
|
||||
{ID: 2, Name: "Tinte", Duration: 45},
|
||||
})
|
||||
})
|
||||
mux.HandleFunc("/index.php/api/v1/customers", func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == http.MethodGet {
|
||||
hits = append(hits, "GET customers")
|
||||
json.NewEncoder(w).Encode([]eaCustomer{}) // no existe → se crea
|
||||
return
|
||||
}
|
||||
hits = append(hits, "POST customers")
|
||||
json.NewEncoder(w).Encode(eaCustomer{ID: 99})
|
||||
})
|
||||
mux.HandleFunc("/index.php/api/v1/appointments", func(w http.ResponseWriter, r *http.Request) {
|
||||
hits = append(hits, "POST appointments")
|
||||
json.NewDecoder(r.Body).Decode(&gotAppt)
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
w.Write([]byte(`{"id":5}`))
|
||||
})
|
||||
srv := httptest.NewServer(mux)
|
||||
defer srv.Close()
|
||||
|
||||
ea := &eaClient{base: srv.URL, http: srv.Client()}
|
||||
|
||||
body := `{"nombre":"Ana López","email":"ana@x.com","telefono":"600","mensaje":"hola",
|
||||
"providerId":1,"serviceIds":[1,2],"date":"2026-07-01","time":"10:00"}`
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/book", strings.NewReader(body))
|
||||
rec := httptest.NewRecorder()
|
||||
handleBook(ea)(rec, req)
|
||||
|
||||
if rec.Code != http.StatusCreated {
|
||||
t.Fatalf("status = %d, want 201; body=%s", rec.Code, rec.Body.String())
|
||||
}
|
||||
// Servicio principal = el de mayor duración (Tinte, id 2).
|
||||
if gotAppt.ServiceID != 2 {
|
||||
t.Errorf("serviceId = %d, want 2 (mayor duración)", gotAppt.ServiceID)
|
||||
}
|
||||
// Fin = inicio + 30 + 45 = 75 min.
|
||||
if gotAppt.Start != "2026-07-01 10:00:00" || gotAppt.End != "2026-07-01 11:15:00" {
|
||||
t.Errorf("start/end = %q/%q, want 10:00:00/11:15:00", gotAppt.Start, gotAppt.End)
|
||||
}
|
||||
if gotAppt.CustomerID != 99 || gotAppt.ProviderID != 1 || gotAppt.Status != "booked" {
|
||||
t.Errorf("appt = %+v", gotAppt)
|
||||
}
|
||||
if !strings.Contains(gotAppt.Notes, "Duración total: 75 min") ||
|
||||
!strings.Contains(gotAppt.Notes, "Servicios: Corte + Tinte") {
|
||||
t.Errorf("notes = %q", gotAppt.Notes)
|
||||
}
|
||||
// Toda la gestión pasó por EA y nada más.
|
||||
sort.Strings(hits)
|
||||
want := []string{"GET customers", "GET services", "POST appointments", "POST customers"}
|
||||
if strings.Join(hits, ",") != strings.Join(want, ",") {
|
||||
t.Errorf("llamadas a EA = %v, want %v", hits, want)
|
||||
}
|
||||
}
|
||||
|
||||
// Servicio inválido (no existe en EA) ⇒ 400 y NO se crea ninguna cita.
|
||||
func TestHandleBookRejectsUnknownService(t *testing.T) {
|
||||
created := false
|
||||
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/appointments", func(w http.ResponseWriter, r *http.Request) {
|
||||
created = true
|
||||
})
|
||||
srv := httptest.NewServer(mux)
|
||||
defer srv.Close()
|
||||
ea := &eaClient{base: srv.URL, http: srv.Client()}
|
||||
|
||||
body := `{"nombre":"Ana","email":"a@x.com","telefono":"600","providerId":1,"serviceIds":[999],"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", rec.Code)
|
||||
}
|
||||
if created {
|
||||
t.Error("se creó una cita con un servicio inexistente")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user