chore: prepare docfill project for gitea

This commit is contained in:
赵义仑
2026-06-24 09:45:35 +08:00
parent ca444bd5d0
commit 32e74d0f43
38 changed files with 2915 additions and 162 deletions

View File

@@ -2,6 +2,7 @@ package docfill
import (
"database/sql"
"errors"
"os"
"path/filepath"
"strings"
@@ -176,6 +177,16 @@ func TestRunRefusesToOverwriteUnlessConfigured(t *testing.T) {
if err == nil || !strings.Contains(err.Error(), "already exists") {
t.Fatalf("expected already exists error, got %v", err)
}
var appErr *AppError
if !errors.As(err, &appErr) {
t.Fatalf("expected AppError, got %T", err)
}
if appErr.Code != CodeOutputExists {
t.Fatalf("error code = %s, want %s", appErr.Code, CodeOutputExists)
}
if appErr.Hint == "" || appErr.Context["output"] != outputPath {
t.Fatalf("expected hint and output context, got %#v", appErr)
}
writeJSON(t, configPath, map[string]any{
"data": map[string]any{
@@ -226,6 +237,19 @@ func TestRunFailsWhenTemplatePlaceholderIsMissing(t *testing.T) {
if err == nil || !strings.Contains(err.Error(), "missing template field") {
t.Fatalf("expected missing template field error, got %v", err)
}
var appErr *AppError
if !errors.As(err, &appErr) {
t.Fatalf("expected AppError, got %T", err)
}
if appErr.Code != CodeTemplateFieldMissing {
t.Fatalf("error code = %s, want %s", appErr.Code, CodeTemplateFieldMissing)
}
if appErr.Context["field"] != "missing" || appErr.Context["template"] != templatePath {
t.Fatalf("unexpected context: %#v", appErr.Context)
}
if !strings.Contains(appErr.Hint, "missing") {
t.Fatalf("unexpected hint: %q", appErr.Hint)
}
}
func TestRunFailsWhenRenderedTemplateStillHasPlaceholder(t *testing.T) {
@@ -259,6 +283,282 @@ func TestRunFailsWhenRenderedTemplateStillHasPlaceholder(t *testing.T) {
}
}
func TestRunFiltersExcelRowsByMultipleEqualsAndVars(t *testing.T) {
dir := t.TempDir()
dataPath := filepath.Join(dir, "tickets.xlsx")
templatePath := filepath.Join(dir, "ticket-template.xlsx")
configPath := filepath.Join(dir, "task.json")
writeWorkbook(t, dataPath, "工单", [][]string{
{"日期", "工单编号", "客户名称", "处理结果"},
{"2026-06-18", "GD-001", "A 公司", "已派单"},
{"2026-06-18", "GD-002", "B 公司", "已完成"},
{"2026-06-19", "GD-001", "C 公司", "待复核"},
})
writeWorkbook(t, templatePath, "工单", [][]string{
{"编号", "{{工单编号}}"},
{"客户", "{{客户名称}}"},
{"结果", "{{处理结果}}"},
})
writeJSON(t, configPath, map[string]any{
"vars": map[string]any{
"date": "2026-06-17",
},
"data": map[string]any{
"type": "excel",
"path": dataPath,
"sheet": "工单",
},
"filter": map[string]any{
"equals": map[string]any{
"日期": "{{date}}",
"工单编号": "{{ticket_no}}",
},
},
"template": map[string]any{
"path": templatePath,
},
"output": map[string]any{
"path": filepath.Join(dir, "out", "工单_{{日期}}_{{工单编号}}.xlsx"),
},
})
result, err := RunWithOptions(configPath, RunOptions{
Vars: map[string]string{
"date": "2026-06-18",
"ticket_no": "GD-002",
},
})
if err != nil {
t.Fatalf("RunWithOptions returned error: %v", err)
}
wantPath := filepath.Join(dir, "out", "工单_2026-06-18_GD-002.xlsx")
if len(result.Files) != 1 || result.Files[0] != wantPath {
t.Fatalf("result files = %#v, want only %q", result.Files, wantPath)
}
assertWorkbookCell(t, wantPath, "工单", "B2", "B 公司")
assertWorkbookCell(t, wantPath, "工单", "B3", "已完成")
}
func TestRunFilterAllowsManyRowsWhenConfigured(t *testing.T) {
dir := t.TempDir()
dbPath := filepath.Join(dir, "tickets.db")
templatePath := filepath.Join(dir, "ticket-template.xlsx")
configPath := filepath.Join(dir, "task.json")
seedP0SQLite(t, dbPath, `
create table tickets (
report_date text,
department text,
ticket_no text,
result text
);
insert into tickets values
('2026-06-18', '营销部', 'GD-001', '已派单'),
('2026-06-18', '客服部', 'GD-002', '已完成'),
('2026-06-19', '营销部', 'GD-003', '待复核');
`)
writeWorkbook(t, templatePath, "工单", [][]string{
{"编号", "{{ticket_no}}"},
{"部门", "{{department}}"},
{"结果", "{{result}}"},
})
writeJSON(t, configPath, map[string]any{
"data": map[string]any{
"type": "sqlite",
"path": dbPath,
"query": "select report_date, department, ticket_no, result from tickets order by ticket_no",
},
"filter": map[string]any{
"equals": map[string]any{
"report_date": "{{date}}",
},
"expect": "many",
},
"template": map[string]any{
"path": templatePath,
},
"output": map[string]any{
"path": filepath.Join(dir, "out", "工单_{{ticket_no}}_{{_row}}.xlsx"),
},
})
result, err := RunWithOptions(configPath, RunOptions{Vars: map[string]string{"date": "2026-06-18"}})
if err != nil {
t.Fatalf("RunWithOptions returned error: %v", err)
}
if len(result.Files) != 2 {
t.Fatalf("expected 2 filtered files, got %#v", result.Files)
}
assertWorkbookCell(t, filepath.Join(dir, "out", "工单_GD-001_1.xlsx"), "工单", "B3", "已派单")
assertWorkbookCell(t, filepath.Join(dir, "out", "工单_GD-002_2.xlsx"), "工单", "B2", "客服部")
}
func TestRunFilterFailsWhenNoRowsMatch(t *testing.T) {
dir := t.TempDir()
dataPath := filepath.Join(dir, "tickets.xlsx")
templatePath := filepath.Join(dir, "template.xlsx")
configPath := filepath.Join(dir, "task.json")
writeWorkbook(t, dataPath, "工单", [][]string{
{"日期", "工单编号", "处理结果"},
{"2026-06-18", "GD-001", "已派单"},
})
writeWorkbook(t, templatePath, "工单", [][]string{{"{{工单编号}}", "{{处理结果}}"}})
writeJSON(t, configPath, map[string]any{
"data": map[string]any{
"type": "excel",
"path": dataPath,
"sheet": "工单",
},
"filter": map[string]any{
"equals": map[string]any{
"日期": "{{date}}",
"工单编号": "{{ticket_no}}",
},
"expect": "one",
},
"template": map[string]any{
"path": templatePath,
},
"output": map[string]any{
"path": filepath.Join(dir, "out", "工单_{{工单编号}}.xlsx"),
},
})
err := RunWithOptionsError(configPath, RunOptions{Vars: map[string]string{
"date": "2026-06-18",
"ticket_no": "GD-999",
}})
assertAppErrorCode(t, err, ErrorCode("DATA_FILTER_NO_MATCH"))
}
func TestRunFilterFailsWhenOneRowExpectedButManyMatch(t *testing.T) {
dir := t.TempDir()
dataPath := filepath.Join(dir, "tickets.xlsx")
templatePath := filepath.Join(dir, "template.xlsx")
configPath := filepath.Join(dir, "task.json")
writeWorkbook(t, dataPath, "工单", [][]string{
{"日期", "工单编号", "处理结果"},
{"2026-06-18", "GD-001", "已派单"},
{"2026-06-18", "GD-002", "已完成"},
})
writeWorkbook(t, templatePath, "工单", [][]string{{"{{工单编号}}", "{{处理结果}}"}})
writeJSON(t, configPath, map[string]any{
"data": map[string]any{
"type": "excel",
"path": dataPath,
"sheet": "工单",
},
"filter": map[string]any{
"equals": map[string]any{
"日期": "{{date}}",
},
"expect": "one",
},
"template": map[string]any{
"path": templatePath,
},
"output": map[string]any{
"path": filepath.Join(dir, "out", "工单_{{工单编号}}.xlsx"),
},
})
err := RunWithOptionsError(configPath, RunOptions{Vars: map[string]string{"date": "2026-06-18"}})
assertAppErrorCode(t, err, ErrorCode("DATA_FILTER_MULTI_MATCH"))
}
func TestRunFilterFailsWhenFieldIsMissing(t *testing.T) {
dir := t.TempDir()
dataPath := filepath.Join(dir, "tickets.xlsx")
templatePath := filepath.Join(dir, "template.xlsx")
configPath := filepath.Join(dir, "task.json")
writeWorkbook(t, dataPath, "工单", [][]string{
{"日期", "处理结果"},
{"2026-06-18", "已派单"},
})
writeWorkbook(t, templatePath, "工单", [][]string{{"{{处理结果}}"}})
writeJSON(t, configPath, map[string]any{
"data": map[string]any{
"type": "excel",
"path": dataPath,
"sheet": "工单",
},
"filter": map[string]any{
"equals": map[string]any{
"工单编号": "{{ticket_no}}",
},
},
"template": map[string]any{
"path": templatePath,
},
"output": map[string]any{
"path": filepath.Join(dir, "out", "工单.xlsx"),
},
})
err := RunWithOptionsError(configPath, RunOptions{Vars: map[string]string{"ticket_no": "GD-001"}})
assertAppErrorCode(t, err, ErrorCode("DATA_FILTER_FIELD"))
}
func TestRunFilterFailsWhenVariableIsMissing(t *testing.T) {
dir := t.TempDir()
dataPath := filepath.Join(dir, "tickets.xlsx")
templatePath := filepath.Join(dir, "template.xlsx")
configPath := filepath.Join(dir, "task.json")
writeWorkbook(t, dataPath, "工单", [][]string{
{"日期", "工单编号", "处理结果"},
{"2026-06-18", "GD-001", "已派单"},
})
writeWorkbook(t, templatePath, "工单", [][]string{{"{{工单编号}}", "{{处理结果}}"}})
writeJSON(t, configPath, map[string]any{
"data": map[string]any{
"type": "excel",
"path": dataPath,
"sheet": "工单",
},
"filter": map[string]any{
"equals": map[string]any{
"工单编号": "{{ticket_no}}",
},
},
"template": map[string]any{
"path": templatePath,
},
"output": map[string]any{
"path": filepath.Join(dir, "out", "工单_{{工单编号}}.xlsx"),
},
})
err := RunWithOptionsError(configPath, RunOptions{})
assertAppErrorCode(t, err, ErrorCode("DATA_FILTER_VALUE"))
}
func RunWithOptionsError(configPath string, opts RunOptions) error {
_, err := RunWithOptions(configPath, opts)
return err
}
func assertAppErrorCode(t *testing.T, err error, want ErrorCode) {
t.Helper()
if err == nil {
t.Fatalf("expected %s error, got nil", want)
}
var appErr *AppError
if !errors.As(err, &appErr) {
t.Fatalf("expected AppError, got %T: %v", err, err)
}
if appErr.Code != want {
t.Fatalf("error code = %s, want %s; error=%v", appErr.Code, want, err)
}
}
func seedP0SQLite(t *testing.T, path, statements string) {
t.Helper()