Files
cmautobuy/admin/syb/ocr_test.go
T

224 lines
7.2 KiB
Go
Raw Normal View History

package syb
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"time"
)
// `[必须]` 本文件全部用 httptest 起假 OCR 服务端,绝不能打真实的
// ocr.ilapage.cn——见工单 #47 和 admin/AGENTS.md。
func ocrBody(code int, message, data string) []byte {
b, _ := json.Marshal(map[string]any{"code": code, "message": message, "data": data})
return b
}
// ── 三种约定的假响应(工单 #47「怎么验证」逐条要求) ──────────────
func TestOcrClient_Recognize_识别成功(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write(ocrBody(200, "Success", "kycv"))
}))
defer srv.Close()
ocr, err := NewOcrClient(srv.URL, time.Second)
if err != nil {
t.Fatalf("创建 OCR 客户端失败: %v", err)
}
data, err := ocr.Recognize(context.Background(), []byte("fake-image"))
if err != nil {
t.Fatalf("识别应该成功,实际报错: %v", err)
}
if data != "kycv" {
t.Errorf("识别结果应该是 kycv,实际 %q", data)
}
}
func TestOcrClient_Recognize_code200但data为空不是错误(t *testing.T) {
// `[必须]` 实测:无文字图片探测得到 code:200 + data:"",这不是
// HTTP/JSON 错误,Recognize 应该返回 (空字符串, nil),由调用方
// (LoginWithOCR)的长度校验去判定"这次没识别出来"。
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write(ocrBody(200, "Success", ""))
}))
defer srv.Close()
ocr, err := NewOcrClient(srv.URL, time.Second)
if err != nil {
t.Fatalf("创建 OCR 客户端失败: %v", err)
}
data, err := ocr.Recognize(context.Background(), []byte("fake-image"))
if err != nil {
t.Fatalf("code:200 + data:\"\" 不应该被当成错误,实际报错: %v", err)
}
if data != "" {
t.Errorf("期望空字符串,实际 %q", data)
}
}
func TestOcrClient_Recognize_长度不对时原样返回交给调用方校验(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write(ocrBody(200, "Success", "ab"))
}))
defer srv.Close()
ocr, err := NewOcrClient(srv.URL, time.Second)
if err != nil {
t.Fatalf("创建 OCR 客户端失败: %v", err)
}
data, err := ocr.Recognize(context.Background(), []byte("fake-image"))
if err != nil {
t.Fatalf("HTTP 调用本身没问题,不应该报错,实际: %v", err)
}
if data != "ab" {
t.Errorf("Recognize 不做长度校验,应该原样返回,实际 %q", data)
}
}
// ── 请求内容:字段名 file,multipart 上传,不落盘 ──────────────
func TestOcrClient_Recognize_用multipart字段名file上传(t *testing.T) {
var gotFieldName string
var gotBytes []byte
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if err := r.ParseMultipartForm(1 << 20); err != nil {
t.Errorf("解析 multipart 表单失败: %v", err)
}
if r.MultipartForm != nil && len(r.MultipartForm.File) > 0 {
for name, files := range r.MultipartForm.File {
gotFieldName = name
if len(files) > 0 {
f, _ := files[0].Open()
gotBytes = make([]byte, files[0].Size)
f.Read(gotBytes)
f.Close()
}
}
}
w.Write(ocrBody(200, "Success", "abcd"))
}))
defer srv.Close()
ocr, _ := NewOcrClient(srv.URL, time.Second)
image := []byte("这是验证码图片的字节内容")
if _, err := ocr.Recognize(context.Background(), image); err != nil {
t.Fatalf("识别失败: %v", err)
}
if gotFieldName != "file" {
t.Errorf("multipart 字段名应该是 file,实际 %q", gotFieldName)
}
if string(gotBytes) != string(image) {
t.Errorf("上传的图片字节应该原样传到服务端,实际 %q", string(gotBytes))
}
}
// ── 降级路径:不可达 / 非 JSON / 非 200 ──────────────────────
func TestOcrClient_Recognize_服务不可达返回error(t *testing.T) {
// 关掉的服务器:地址存在但连不上。
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
srv.Close()
ocr, err := NewOcrClient(srv.URL, 500*time.Millisecond)
if err != nil {
t.Fatalf("创建 OCR 客户端失败: %v", err)
}
_, err = ocr.Recognize(context.Background(), []byte("fake-image"))
if err == nil {
t.Fatal("服务连不上应该返回 error")
}
}
func TestOcrClient_Recognize_非JSON响应返回error不panic(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("<html>不是 JSON</html>"))
}))
defer srv.Close()
ocr, _ := NewOcrClient(srv.URL, time.Second)
_, err := ocr.Recognize(context.Background(), []byte("fake-image"))
if err == nil {
t.Fatal("非 JSON 响应应该返回 error")
}
}
func TestOcrClient_Recognize_HTTP非200返回error(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte("internal error"))
}))
defer srv.Close()
ocr, _ := NewOcrClient(srv.URL, time.Second)
_, err := ocr.Recognize(context.Background(), []byte("fake-image"))
if err == nil {
t.Fatal("HTTP 500 应该返回 error")
}
}
func TestOcrClient_Recognize_业务code非200返回error(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write(ocrBody(500, "内部错误", ""))
}))
defer srv.Close()
ocr, _ := NewOcrClient(srv.URL, time.Second)
_, err := ocr.Recognize(context.Background(), []byte("fake-image"))
if err == nil {
t.Fatal("code != 200 应该返回 error")
}
}
func TestNewOcrClient_地址留空报错(t *testing.T) {
if _, err := NewOcrClient("", time.Second); err == nil {
t.Fatal("空地址应该报错——调用方应该在这之前就判断 ocr_url 是否留空并跳过 OCR")
}
if _, err := NewOcrClient(" ", time.Second); err == nil {
t.Fatal("空白地址同样应该报错")
}
}
// ── OCR 请求不带顺运宝 Cookie:独立 http.Client ──────────────
func TestOcrClient_不携带顺运宝的Cookie(t *testing.T) {
var gotCookie string
sybSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.SetCookie(w, &http.Cookie{Name: "erp_session", Value: "should-not-leak", Path: "/"})
w.Header().Set("Content-Type", "image/jpeg")
w.Write([]byte("fake-jpeg"))
}))
defer sybSrv.Close()
ocrSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if ck, err := r.Cookie("erp_session"); err == nil {
gotCookie = ck.Value
}
w.Write(ocrBody(200, "Success", "abcd"))
}))
defer ocrSrv.Close()
sybClient, err := New(sybSrv.URL)
if err != nil {
t.Fatalf("创建顺运宝客户端失败: %v", err)
}
captcha, err := sybClient.FetchCaptcha(context.Background())
if err != nil {
t.Fatalf("获取验证码失败: %v", err)
}
ocrClient, err := NewOcrClient(ocrSrv.URL, time.Second)
if err != nil {
t.Fatalf("创建 OCR 客户端失败: %v", err)
}
if _, err := ocrClient.Recognize(context.Background(), captcha.Image); err != nil {
t.Fatalf("识别失败: %v", err)
}
if gotCookie != "" {
t.Fatalf("OCR 请求不应该带上顺运宝的 Cookie,实际带了 %q", gotCookie)
}
}