package docfill import ( "database/sql" "os" "path/filepath" "strings" "testing" ) func TestRunResolvesConfigRelativePathsAndSanitizesOutputFileNames(t *testing.T) { dir := t.TempDir() taskDir := filepath.Join(dir, "task") if err := os.MkdirAll(taskDir, 0o755); err != nil { t.Fatalf("make task dir: %v", err) } writeWorkbook(t, filepath.Join(taskDir, "data.xlsx"), "Daily", [][]string{ {"name", "done"}, {"张/三:测试", "已完成"}, }) writeWorkbook(t, filepath.Join(taskDir, "template.xlsx"), "日报", [][]string{ {"{{name}}", "{{done}}"}, }) configPath := filepath.Join(taskDir, "task.json") writeJSON(t, configPath, map[string]any{ "data": map[string]any{ "type": "excel", "path": "./data.xlsx", "sheet": "Daily", }, "template": map[string]any{ "path": "./template.xlsx", }, "output": map[string]any{ "path": "./out/日报_{{name}}.xlsx", }, }) result, err := RunWithOptions(configPath, RunOptions{}) if err != nil { t.Fatalf("RunWithOptions returned error: %v", err) } wantPath := filepath.Join(taskDir, "out", "日报_张_三_测试.xlsx") if len(result.Files) != 1 || result.Files[0] != wantPath { t.Fatalf("result files = %#v, want %q", result.Files, wantPath) } assertWorkbookCell(t, wantPath, "日报", "A1", "张/三:测试") } func TestRunUsesVarsForSQLiteParamsAndTemplateDefaults(t *testing.T) { dir := t.TempDir() dbPath := filepath.Join(dir, "daily.db") templatePath := filepath.Join(dir, "template.docx") configPath := filepath.Join(dir, "task.json") seedP0SQLite(t, dbPath, ` create table daily (name text, date text, done text); insert into daily values ('张三', '2026-06-18', '完成旧日报'), ('李四', '2026-06-19', '完成新日报'); `) writeMinimalDocx(t, templatePath, "批次:{{batch}}\n日期:{{date}}\n员工:{{name}}\n内容:{{done}}") writeJSON(t, configPath, map[string]any{ "vars": map[string]any{ "date": "2026-06-18", "batch": "默认批次", }, "data": map[string]any{ "type": "sqlite", "path": dbPath, "query": "select name, date, done from daily where date = ?", "params": []any{"{{date}}"}, }, "template": map[string]any{ "path": templatePath, }, "output": map[string]any{ "path": filepath.Join(dir, "out", "日报_{{batch}}_{{date}}_{{name}}.docx"), }, }) result, err := RunWithOptions(configPath, RunOptions{ Vars: map[string]string{ "date": "2026-06-19", "batch": "命令行批次", }, }) if err != nil { t.Fatalf("RunWithOptions returned error: %v", err) } if len(result.Files) != 1 { t.Fatalf("expected 1 generated file, got %#v", result.Files) } got := readZipEntry(t, result.Files[0], "word/document.xml") if !strings.Contains(got, "批次:命令行批次") || !strings.Contains(got, "员工:李四") { t.Fatalf("unexpected rendered docx body: %q", got) } if strings.Contains(got, "张三") || strings.Contains(result.Files[0], "默认批次") { t.Fatalf("CLI vars did not override config vars: file=%q body=%q", result.Files[0], got) } } func TestRunSupportsExcelHeaderRow(t *testing.T) { dir := t.TempDir() dataPath := filepath.Join(dir, "data.xlsx") templatePath := filepath.Join(dir, "template.xlsx") configPath := filepath.Join(dir, "task.json") writeWorkbook(t, dataPath, "Daily", [][]string{ {"日报导出数据"}, {"name", "done"}, {"张三", "完成日报"}, }) writeWorkbook(t, templatePath, "日报", [][]string{ {"员工", "{{name}}"}, {"完成", "{{done}}"}, }) writeJSON(t, configPath, map[string]any{ "data": map[string]any{ "type": "excel", "path": dataPath, "sheet": "Daily", "header_row": 2, }, "template": map[string]any{ "path": templatePath, }, "output": map[string]any{ "path": filepath.Join(dir, "out", "日报_{{name}}.xlsx"), }, }) if err := Run(configPath); err != nil { t.Fatalf("Run returned error: %v", err) } assertWorkbookCell(t, filepath.Join(dir, "out", "日报_张三.xlsx"), "日报", "B1", "张三") } func TestRunRefusesToOverwriteUnlessConfigured(t *testing.T) { dir := t.TempDir() dataPath := filepath.Join(dir, "data.xlsx") templatePath := filepath.Join(dir, "template.xlsx") configPath := filepath.Join(dir, "task.json") outputPath := filepath.Join(dir, "out", "日报_张三.xlsx") writeWorkbook(t, dataPath, "Daily", [][]string{ {"name"}, {"张三"}, }) writeWorkbook(t, templatePath, "日报", [][]string{{"{{name}}"}}) if err := os.MkdirAll(filepath.Dir(outputPath), 0o755); err != nil { t.Fatalf("make output dir: %v", err) } if err := os.WriteFile(outputPath, []byte("existing"), 0o644); err != nil { t.Fatalf("write existing output: %v", err) } 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": outputPath, }, }) err := Run(configPath) if err == nil || !strings.Contains(err.Error(), "already exists") { t.Fatalf("expected already exists error, got %v", err) } 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": outputPath, "overwrite": true, }, }) if err := Run(configPath); err != nil { t.Fatalf("Run with overwrite returned error: %v", err) } assertWorkbookCell(t, outputPath, "日报", "A1", "张三") } func TestRunFailsWhenTemplatePlaceholderIsMissing(t *testing.T) { dir := t.TempDir() dataPath := filepath.Join(dir, "data.xlsx") templatePath := filepath.Join(dir, "template.xlsx") configPath := filepath.Join(dir, "task.json") writeWorkbook(t, dataPath, "Daily", [][]string{ {"name"}, {"张三"}, }) writeWorkbook(t, templatePath, "日报", [][]string{{"{{name}}", "{{missing}}"}}) 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": filepath.Join(dir, "out", "日报_{{name}}.xlsx"), }, }) err := Run(configPath) if err == nil || !strings.Contains(err.Error(), "missing template field") { t.Fatalf("expected missing template field error, got %v", err) } } func TestRunFailsWhenRenderedTemplateStillHasPlaceholder(t *testing.T) { dir := t.TempDir() dataPath := filepath.Join(dir, "data.xlsx") templatePath := filepath.Join(dir, "template.xlsx") configPath := filepath.Join(dir, "task.json") writeWorkbook(t, dataPath, "Daily", [][]string{ {"name", "done"}, {"张三", "{{still_pending}}"}, }) writeWorkbook(t, templatePath, "日报", [][]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": filepath.Join(dir, "out", "日报_{{name}}.xlsx"), }, }) err := Run(configPath) if err == nil || !strings.Contains(err.Error(), "unresolved placeholder") { t.Fatalf("expected unresolved placeholder error, got %v", err) } } func seedP0SQLite(t *testing.T, path, statements string) { t.Helper() db, err := sql.Open("sqlite", path) if err != nil { t.Fatalf("open sqlite: %v", err) } defer db.Close() if _, err := db.Exec(statements); err != nil { t.Fatalf("seed sqlite: %v", err) } }