package services import ( "archive/zip" "bytes" "encoding/xml" "io" "strings" "testing" ) // El .xlsx se arma a mano, así que hay que confirmar que es un zip válido con // todas las partes y XML bien formado — Excel no perdona ninguna de las dos. func TestEscribirXLSX(t *testing.T) { filas := [][]string{ {"#", "Fase", "Descripción", "Estado"}, {"1", "Diseño & maquetación", "Wireframes ", "Completado"}, {"2", "Integración API", "Conectar \"pasarela\" de pago", "En progreso"}, } var buf bytes.Buffer if err := EscribirXLSX(&buf, "Cronograma", filas); err != nil { t.Fatalf("EscribirXLSX: %v", err) } z, err := zip.NewReader(bytes.NewReader(buf.Bytes()), int64(buf.Len())) if err != nil { t.Fatalf("no es un zip válido: %v", err) } requeridas := []string{ "[Content_Types].xml", "_rels/.rels", "xl/workbook.xml", "xl/_rels/workbook.xml.rels", "xl/styles.xml", "xl/worksheets/sheet1.xml", } presentes := map[string]string{} for _, f := range z.File { rc, _ := f.Open() b, _ := io.ReadAll(rc) rc.Close() presentes[f.Name] = string(b) } for _, r := range requeridas { if _, ok := presentes[r]; !ok { t.Errorf("falta la parte %s", r) } } // Todo XML debe parsear: un & o un < sin escapar rompe el archivo entero. for nombre, cuerpo := range presentes { d := xml.NewDecoder(strings.NewReader(cuerpo)) for { _, err := d.Token() if err == io.EOF { break } if err != nil { t.Fatalf("XML inválido en %s: %v", nombre, err) } } } hoja := presentes["xl/worksheets/sheet1.xml"] if !strings.Contains(hoja, "Diseño & maquetación") { t.Error("el & del texto no quedó escapado") } if !strings.Contains(hoja, "Wireframes <UX>") { t.Error("los < > del texto no quedaron escapados") } if !strings.Contains(hoja, `