Los tickets del portal ya avisaban por Telegram; los que entran por correo solo mandaban un mail al admin, que es justo el canal que nadie mira a tiempo. Ahora pasan por el mismo despachador de notificaciones, así que se prenden y apagan desde /app/notif-config como cualquier otro evento — nuevo ticket y respuesta del cliente por separado. El cuerpo del aviso es un resumen cuando el correo pasa los 700 caracteres (hilos reenviados, texto pegado); abajo de eso va tal cual, porque resumir tres renglones es gastar una llamada de IA para decir lo mismo. Si la IA falla, se manda recortado. El ticket guarda siempre el texto completo. DispatchTicketNuevo ahora acepta portalUser nil: un correo no tiene usuario de portal detrás y no por eso hay que dejar de avisar. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
93 lines
2.6 KiB
Go
93 lines
2.6 KiB
Go
package services
|
|
|
|
import (
|
|
"testing"
|
|
)
|
|
|
|
func TestExtractEmailSimple(t *testing.T) {
|
|
tests := []struct {
|
|
input string
|
|
want string
|
|
}{
|
|
{"user@example.com", "user@example.com"},
|
|
{"<user@example.com>", "user@example.com"},
|
|
{"John Doe <john@example.com>", "john@example.com"},
|
|
{"\"John Doe\" <john@example.com>", "john@example.com"},
|
|
{" spaced@example.com ", "spaced@example.com"},
|
|
{"", ""},
|
|
{"<onlybrackets>", "onlybrackets"},
|
|
}
|
|
for _, tt := range tests {
|
|
got := ExtraerEmail(tt.input)
|
|
if got != tt.want {
|
|
t.Errorf("ExtraerEmail(%q) = %q, want %q", tt.input, got, tt.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestExtractNameSimple(t *testing.T) {
|
|
tests := []struct {
|
|
input string
|
|
want string
|
|
}{
|
|
{"John Doe <john@example.com>", "John Doe"},
|
|
{"<user@example.com>", ""},
|
|
{"user@example.com", ""},
|
|
{" Spaces Here <spaces@example.com>", "Spaces Here"},
|
|
{"\"Quoted Name\" <q@example.com>", "\"Quoted Name\""},
|
|
{"", ""},
|
|
}
|
|
for _, tt := range tests {
|
|
got := ExtraerNombre(tt.input)
|
|
if got != tt.want {
|
|
t.Errorf("ExtraerNombre(%q) = %q, want %q", tt.input, got, tt.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestExtractEmailRealWorld(t *testing.T) {
|
|
inputs := []struct {
|
|
full string
|
|
mail string
|
|
name string
|
|
}{
|
|
{"María López <maria@example.com>", "maria@example.com", "María López"},
|
|
{"soporte@u-s.app", "soporte@u-s.app", ""},
|
|
{"Cliente Final <cliente+tag@dominio.co>", "cliente+tag@dominio.co", "Cliente Final"},
|
|
{"", "", ""},
|
|
}
|
|
for _, tt := range inputs {
|
|
gotMail := ExtraerEmail(tt.full)
|
|
gotName := ExtraerNombre(tt.full)
|
|
if gotMail != tt.mail {
|
|
t.Errorf("ExtraerEmail(%q) = %q, want %q", tt.full, gotMail, tt.mail)
|
|
}
|
|
if gotName != tt.name {
|
|
t.Errorf("ExtraerNombre(%q) = %q, want %q", tt.full, gotName, tt.name)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestExtractEmailEdgeCases(t *testing.T) {
|
|
// Formato RFC 5322 con nombre y ángulos
|
|
if got := ExtraerEmail("a<b@c.com>"); got != "b@c.com" {
|
|
t.Errorf("ExtraerEmail('a<b@c.com>') = %q, want 'b@c.com'", got)
|
|
}
|
|
// Múltiples brackets — usa el último par
|
|
if got := ExtraerEmail("<a><b@c.com>"); got != "b@c.com" {
|
|
t.Errorf("ExtraerEmail('<a><b@c.com>') = %q, want 'b@c.com'", got)
|
|
}
|
|
}
|
|
|
|
func TestResumirTextoSoporteCortoNoLlamaALaIA(t *testing.T) {
|
|
// Sin base de datos, cualquier llamada a la IA explota: que este caso pase
|
|
// es la prueba de que un correo corto no la usa.
|
|
corto := "Hola, no me llega la factura de marzo. Gracias."
|
|
if got := ResumirTextoSoporte(corto); got != corto {
|
|
t.Errorf("ResumirTextoSoporte devolvió %q, esperaba el texto tal cual", got)
|
|
}
|
|
if got := ResumirTextoSoporte(" " + corto + "\n"); got != corto {
|
|
t.Errorf("no recortó los espacios: %q", got)
|
|
}
|
|
}
|