101 lines
3.6 KiB
Go
101 lines
3.6 KiB
Go
|
|
package logic
|
|||
|
|
|
|||
|
|
import (
|
|||
|
|
"context"
|
|||
|
|
"os"
|
|||
|
|
"path/filepath"
|
|||
|
|
"strings"
|
|||
|
|
"testing"
|
|||
|
|
|
|||
|
|
"github.com/gogf/gf/v2/frame/g"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// TestSaveQrcodeImage 验证「小程序码落图片库」契约(迭代-2026-10-09)。
|
|||
|
|
//
|
|||
|
|
// 关注四点:
|
|||
|
|
// 1. 落盘位置在图片库 manifest/static/qrcode/<ym>/<md5>.png(不是 qrcode 私有目录);
|
|||
|
|
// 2. 返回的 relPath 以 /static/ 开头(库内只存相对路径);
|
|||
|
|
// 3. 同一份字节重复调用**不新增文件**(md5 命名天然去重,不会在图片库堆垃圾);
|
|||
|
|
// 4. 无 site.base_url 且无请求上下文时,absURL 回退为相对路径本身(本地联调零配置)。
|
|||
|
|
func TestSaveQrcodeImage(t *testing.T) {
|
|||
|
|
ctx := context.Background()
|
|||
|
|
// storeStatic 会写 manifest/static/...,测试必须在仓库根跑(go test 的 CWD 是包目录)
|
|||
|
|
// 故这里显式切到仓库根。
|
|||
|
|
wd, err := os.Getwd()
|
|||
|
|
if err != nil {
|
|||
|
|
t.Fatalf("Getwd 失败:%v", err)
|
|||
|
|
}
|
|||
|
|
if err = os.Chdir("../.."); err != nil {
|
|||
|
|
t.Fatalf("切到仓库根失败:%v", err)
|
|||
|
|
}
|
|||
|
|
defer func() { _ = os.Chdir(wd) }()
|
|||
|
|
|
|||
|
|
// 造一张最小合法 PNG(1x1),只为验证落盘与命名,不校验图片内容
|
|||
|
|
data := []byte{
|
|||
|
|
0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A,
|
|||
|
|
0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52,
|
|||
|
|
}
|
|||
|
|
relPath, absURL, err := saveQrcodeImage(ctx, "event_probe.png", data)
|
|||
|
|
if err != nil {
|
|||
|
|
t.Fatalf("saveQrcodeImage 失败:%v", err)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if !strings.HasPrefix(relPath, staticURLPrefix+"/") {
|
|||
|
|
t.Fatalf("relPath 必须以 %s/ 开头(库内只存相对路径),实际=%q", staticURLPrefix, relPath)
|
|||
|
|
}
|
|||
|
|
// 落在图片库的 qrcode 子目录下(storeStatic 里 dirName = scene = "qrcode")
|
|||
|
|
if !strings.Contains(relPath, "/qrcode/") {
|
|||
|
|
t.Fatalf("relPath 应落在图片库 qrcode 子目录,实际=%q", relPath)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 落盘文件必须真实存在
|
|||
|
|
disk := filepath.Join(StaticDir, filepath.FromSlash(strings.TrimPrefix(relPath, staticURLPrefix+"/")))
|
|||
|
|
if _, err = os.Stat(disk); err != nil {
|
|||
|
|
t.Fatalf("码图未落盘:%s(%v)", disk, err)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 去重:同一份字节再调一次,文件数不应增加
|
|||
|
|
before := countQrcodeFiles(t)
|
|||
|
|
if _, _, err = saveQrcodeImage(ctx, "event_probe.png", data); err != nil {
|
|||
|
|
t.Fatalf("第二次 saveQrcodeImage 失败:%v", err)
|
|||
|
|
}
|
|||
|
|
after := countQrcodeFiles(t)
|
|||
|
|
if after != before {
|
|||
|
|
t.Fatalf("重复生成应复用同一文件(md5 去重),文件数 %d → %d", before, after)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 无 site.base_url + 无请求上下文 → absURL 回退为相对路径本身
|
|||
|
|
if absURL != relPath {
|
|||
|
|
t.Logf("absURL=%q(未配置 base_url 时回退相对路径,符合预期)", absURL)
|
|||
|
|
}
|
|||
|
|
if !strings.HasPrefix(absURL, "http://") && !strings.HasPrefix(absURL, "https://") && absURL != relPath {
|
|||
|
|
t.Fatalf("absURL 既不是绝对 URL 也不是 relPath:%q", absURL)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 附件表登记(recordAttachment 失败不阻断,故只告警不 Fatal;但成功时应能查到)
|
|||
|
|
if n, e := g.Model("attachments").Where("file_path", relPath).WhereNull("deleted_at").Count(); e == nil {
|
|||
|
|
t.Logf("附件表登记条数=%d(0 表示未登记,可能库未连通,不影响落盘结论)", n)
|
|||
|
|
} else {
|
|||
|
|
t.Logf("附件表查询失败(可能未连库):%v", e)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
_ = os.Remove(disk)
|
|||
|
|
t.Logf("relPath=%s absURL=%s disk=%s", relPath, absURL, disk)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// countQrcodeFiles 统计图片库 qrcode 子目录下的文件数。
|
|||
|
|
func countQrcodeFiles(t *testing.T) int {
|
|||
|
|
t.Helper()
|
|||
|
|
dir := filepath.Join(StaticDir, "qrcode")
|
|||
|
|
var n int
|
|||
|
|
_ = filepath.Walk(dir, func(_ string, info os.FileInfo, err error) error {
|
|||
|
|
if err != nil {
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
if !info.IsDir() {
|
|||
|
|
n++
|
|||
|
|
}
|
|||
|
|
return nil
|
|||
|
|
})
|
|||
|
|
return n
|
|||
|
|
}
|