Files
xls_doc/internal/docfill/docfill_test.go
2026-06-24 09:45:35 +08:00

334 lines
9.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package docfill
import (
"archive/zip"
"database/sql"
"encoding/json"
"io"
"os"
"path/filepath"
"strings"
"testing"
_ "modernc.org/sqlite"
"github.com/xuri/excelize/v2"
)
func TestReadExcelRowsUsesFirstRowAsHeaders(t *testing.T) {
path := filepath.Join(t.TempDir(), "data.xlsx")
writeWorkbook(t, path, "Daily", [][]string{
{"name", "date", "done"},
{"Alice", "2026-06-18", "接口数据已整理"},
{"Bob", "2026-06-18", "报表已提交"},
})
rows, err := readRows(dataConfig{Type: "excel", Path: path, Sheet: "Daily"})
if err != nil {
t.Fatalf("readRows returned error: %v", err)
}
if len(rows) != 2 {
t.Fatalf("expected 2 rows, got %d", len(rows))
}
if rows[0]["name"] != "Alice" || rows[0]["done"] != "接口数据已整理" {
t.Fatalf("unexpected first row: %#v", rows[0])
}
if rows[1]["name"] != "Bob" || rows[1]["date"] != "2026-06-18" {
t.Fatalf("unexpected second row: %#v", rows[1])
}
}
func TestReadSQLiteRowsUsesQueryColumnNames(t *testing.T) {
dbPath := filepath.Join(t.TempDir(), "data.db")
db, err := sql.Open("sqlite", dbPath)
if err != nil {
t.Fatalf("open sqlite: %v", err)
}
defer db.Close()
_, err = db.Exec(`
create table daily (name text, done text);
insert into daily (name, done) values ('Alice', '完成日报'), ('Bob', '完成周报');
`)
if err != nil {
t.Fatalf("seed sqlite: %v", err)
}
rows, err := readRows(dataConfig{
Type: "sqlite",
Path: dbPath,
Query: "select name, done from daily order by name",
})
if err != nil {
t.Fatalf("readRows returned error: %v", err)
}
if len(rows) != 2 {
t.Fatalf("expected 2 rows, got %d", len(rows))
}
if rows[0]["name"] != "Alice" || rows[0]["done"] != "完成日报" {
t.Fatalf("unexpected first row: %#v", rows[0])
}
}
func TestRenderXLSXReplacesPlaceholders(t *testing.T) {
dir := t.TempDir()
templatePath := filepath.Join(dir, "template.xlsx")
outputPath := filepath.Join(dir, "out.xlsx")
writeWorkbook(t, templatePath, "Sheet1", [][]string{
{"姓名", "内容"},
{"{{name}}", "今天完成:{{done}}"},
})
err := renderTemplate(templatePath, outputPath, map[string]string{
"name": "Alice",
"done": "接口数据整理",
})
if err != nil {
t.Fatalf("renderTemplate returned error: %v", err)
}
f, err := excelize.OpenFile(outputPath)
if err != nil {
t.Fatalf("open output: %v", err)
}
defer f.Close()
name, err := f.GetCellValue("Sheet1", "A2")
if err != nil {
t.Fatalf("read A2: %v", err)
}
done, err := f.GetCellValue("Sheet1", "B2")
if err != nil {
t.Fatalf("read B2: %v", err)
}
if name != "Alice" || done != "今天完成:接口数据整理" {
t.Fatalf("unexpected rendered values: A2=%q B2=%q", name, done)
}
}
func TestRenderDOCXReplacesPlaceholdersInXMLParts(t *testing.T) {
dir := t.TempDir()
templatePath := filepath.Join(dir, "template.docx")
outputPath := filepath.Join(dir, "out.docx")
writeMinimalDocx(t, templatePath, "日报:{{name}} 完成 {{done}}")
err := renderTemplate(templatePath, outputPath, map[string]string{
"name": "Alice",
"done": "接口数据整理",
})
if err != nil {
t.Fatalf("renderTemplate returned error: %v", err)
}
got := readZipEntry(t, outputPath, "word/document.xml")
want := "日报Alice 完成 接口数据整理"
if got != want {
t.Fatalf("document.xml = %q, want %q", got, want)
}
}
func TestRenderDOCXReplacesPlaceholderSplitAcrossTextNodes(t *testing.T) {
dir := t.TempDir()
templatePath := filepath.Join(dir, "template.docx")
outputPath := filepath.Join(dir, "out.docx")
writeMinimalDocx(t, templatePath, `<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"><w:body><w:p><w:r><w:t>员工:{{na</w:t></w:r><w:r><w:t>me}}</w:t></w:r><w:r><w:t>,完成:{{done}}</w:t></w:r></w:p></w:body></w:document>`)
err := renderTemplate(templatePath, outputPath, map[string]string{
"name": "Alice",
"done": "接口数据整理",
})
if err != nil {
t.Fatalf("renderTemplate returned error: %v", err)
}
got := readZipEntry(t, outputPath, "word/document.xml")
if !strings.Contains(got, "员工Alice完成接口数据整理") {
t.Fatalf("document.xml = %q, want rendered split placeholder", got)
}
if strings.Contains(got, "{{") || strings.Contains(got, "}}") {
t.Fatalf("document.xml still has placeholder markers: %q", got)
}
}
func TestRenderDOCXReplacesMultipleSplitPlaceholdersInSameParagraph(t *testing.T) {
dir := t.TempDir()
templatePath := filepath.Join(dir, "template.docx")
outputPath := filepath.Join(dir, "out.docx")
writeMinimalDocx(t, templatePath, `<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"><w:body><w:p><w:r><w:t>员工:{{na</w:t></w:r><w:r><w:t>me}}</w:t></w:r><w:r><w:t>,完成:{{do</w:t></w:r><w:r><w:t>ne}}</w:t></w:r><w:r><w:t>,备注:{{note}}</w:t></w:r></w:p></w:body></w:document>`)
err := renderTemplate(templatePath, outputPath, map[string]string{
"name": "Alice",
"done": "接口数据整理",
"note": "保持同段落",
})
if err != nil {
t.Fatalf("renderTemplate returned error: %v", err)
}
got := readZipEntry(t, outputPath, "word/document.xml")
if !strings.Contains(got, "员工Alice完成接口数据整理备注保持同段落") {
t.Fatalf("document.xml = %q, want rendered split placeholders", got)
}
if strings.Contains(got, "{{") || strings.Contains(got, "}}") {
t.Fatalf("document.xml still has placeholder markers: %q", got)
}
}
func TestRunGeneratesOneOutputPerInputRow(t *testing.T) {
dir := t.TempDir()
dataPath := filepath.Join(dir, "data.xlsx")
templatePath := filepath.Join(dir, "template.xlsx")
outputPattern := filepath.Join(dir, "out", "report_{{name}}_{{_row}}.xlsx")
configPath := filepath.Join(dir, "task.json")
writeWorkbook(t, dataPath, "Daily", [][]string{
{"name", "done"},
{"Alice", "日报"},
{"Bob", "周报"},
})
writeWorkbook(t, templatePath, "Sheet1", [][]string{
{"{{name}}", "{{done}}"},
})
writeJSON(t, configPath, map[string]any{
"data": map[string]any{
"type": "excel",
"path": dataPath,
"sheet": "Daily",
},
"template": map[string]any{
"path": templatePath,
},
"output": map[string]any{
"path": outputPattern,
},
})
if err := Run(configPath); err != nil {
t.Fatalf("Run returned error: %v", err)
}
assertWorkbookCell(t, filepath.Join(dir, "out", "report_Alice_1.xlsx"), "Sheet1", "A1", "Alice")
assertWorkbookCell(t, filepath.Join(dir, "out", "report_Bob_2.xlsx"), "Sheet1", "B1", "周报")
}
func writeWorkbook(t *testing.T, path, sheet string, rows [][]string) {
t.Helper()
f := excelize.NewFile()
defer f.Close()
defaultSheet := f.GetSheetName(0)
if defaultSheet != sheet {
if err := f.SetSheetName(defaultSheet, sheet); err != nil {
t.Fatalf("set sheet name: %v", err)
}
}
for r, row := range rows {
for c, value := range row {
cell, err := excelize.CoordinatesToCellName(c+1, r+1)
if err != nil {
t.Fatalf("cell name: %v", err)
}
if err := f.SetCellValue(sheet, cell, value); err != nil {
t.Fatalf("set cell value: %v", err)
}
}
}
if err := f.SaveAs(path); err != nil {
t.Fatalf("save workbook: %v", err)
}
}
func writeMinimalDocx(t *testing.T, path, body string) {
t.Helper()
file, err := os.Create(path)
if err != nil {
t.Fatalf("create docx: %v", err)
}
defer file.Close()
zw := zip.NewWriter(file)
writeZipEntry(t, zw, "[Content_Types].xml", `<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"/>`)
writeZipEntry(t, zw, "word/document.xml", body)
if err := zw.Close(); err != nil {
t.Fatalf("close zip: %v", err)
}
}
func writeZipEntry(t *testing.T, zw *zip.Writer, name, body string) {
t.Helper()
w, err := zw.Create(name)
if err != nil {
t.Fatalf("create zip entry %s: %v", name, err)
}
if _, err := w.Write([]byte(body)); err != nil {
t.Fatalf("write zip entry %s: %v", name, err)
}
}
func readZipEntry(t *testing.T, path, name string) string {
t.Helper()
zr, err := zip.OpenReader(path)
if err != nil {
t.Fatalf("open zip: %v", err)
}
defer zr.Close()
for _, file := range zr.File {
if file.Name != name {
continue
}
rc, err := file.Open()
if err != nil {
t.Fatalf("open zip entry %s: %v", name, err)
}
defer rc.Close()
data, err := io.ReadAll(rc)
if err != nil {
t.Fatalf("read zip entry %s: %v", name, err)
}
return string(data)
}
t.Fatalf("zip entry %s not found", name)
return ""
}
func writeJSON(t *testing.T, path string, value any) {
t.Helper()
data, err := json.MarshalIndent(value, "", " ")
if err != nil {
t.Fatalf("marshal json: %v", err)
}
if err := os.WriteFile(path, data, 0o644); err != nil {
t.Fatalf("write json: %v", err)
}
}
func assertWorkbookCell(t *testing.T, path, sheet, cell, want string) {
t.Helper()
f, err := excelize.OpenFile(path)
if err != nil {
t.Fatalf("open workbook %s: %v", path, err)
}
defer f.Close()
got, err := f.GetCellValue(sheet, cell)
if err != nil {
t.Fatalf("read cell %s: %v", cell, err)
}
if got != want {
t.Fatalf("%s %s = %q, want %q", filepath.Base(path), cell, got, want)
}
}