package services import ( "strings" "testing" "time" "github.com/sujit-baniya/fiber-boilerplate/pkg/models" ) // El detalle que va en el correo tiene que sumar exactamente lo que cobra el // link de pago. Si esto se desincroniza, el cliente recibe un cobro que no // coincide con su propio desglose — una disputa asegurada. func TestItemsDesdeResumenCuadraConElTotal(t *testing.T) { resumen := []models.ResumenUso{ {Tipo: models.UsoTipoIA, Unidad: "tokens", Cantidad: 12000, Costo: 30, Eventos: 8}, {Tipo: models.UsoTipoOCR, Unidad: "imagen", Cantidad: 4, Costo: 40, Eventos: 4}, {Tipo: models.UsoTipoWhisper, Unidad: "transcripcion", Cantidad: 2, Costo: 80, Eventos: 2}, } items := itemsDesdeResumen(resumen, time.Now()) if len(items) != 3 { t.Fatalf("esperaba 3 líneas de detalle, obtuve %d", len(items)) } var total float64 for _, it := range items { total += it.Precio } if total != 150 { t.Errorf("el detalle suma %.2f, esperaba 150 (30+40+80)", total) } // La etiqueta tiene que decir cuánto se consumió, no solo el precio. if !strings.Contains(items[0].Nombre, "12000") || !strings.Contains(items[0].Nombre, "tokens") { t.Errorf("la línea de IA no dice la cantidad consumida: %q", items[0].Nombre) } } // Un consumo en cero no debe aparecer como línea de $0 en el correo. func TestItemsDesdeResumenOmiteCostoCero(t *testing.T) { resumen := []models.ResumenUso{ {Tipo: models.UsoTipoIA, Unidad: "tokens", Cantidad: 500, Costo: 0}, {Tipo: models.UsoTipoOCR, Unidad: "imagen", Cantidad: 1, Costo: 10}, } items := itemsDesdeResumen(resumen, time.Now()) if len(items) != 1 { t.Fatalf("esperaba 1 línea (la de costo 0 se omite), obtuve %d", len(items)) } } // Sin consumo no hay líneas extra: el correo queda igual que antes de que // existiera el cobro por uso. func TestItemsDesdeResumenVacio(t *testing.T) { if items := itemsDesdeResumen(nil, time.Now()); len(items) != 0 { t.Errorf("sin consumo no debería agregar líneas, obtuve %d", len(items)) } }