2ed5dfa6a1
iotlcm-app:小程序端 + 用户端服务。 - app/:uni-app 小程序前端(基于 unibest,MIT) - 微信 AppID → YOUR_WX_APPID,uni-app AppID → __UNI__YOURAPPID - 内网后端地址 → admin.iotlcm.example.com - 应用标题与 unibest 模板 README 重写(保留上游致谢与 MIT 声明) - server/:GoFrame 用户端服务(基于 XiuAdmin,Apache-2.0) - module xiuadmin → git.5iot.com/iotlcm-public/iotlcm-app(360 文件) - 支付回调地址 → app.iotlcm.example.com,商户名与应用名泛化 - 真实手机号 → 合法 11 位编造号(保留号段前 3 位 + 后 4 位) - 依赖号码尾部的断言同步更新 - 补 server/LICENSE(Apache-2.0) - 移除内部资产:CI 触发器 - 补根 README,LF 行尾 + .gitattributes 已知:TestSysDept_RefreshDeptAncestors 在源仓同一提交上同样失败,属既有缺陷。
165 lines
5.4 KiB
Go
165 lines
5.4 KiB
Go
package app
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
type timeoutNetError struct{}
|
|
|
|
func (timeoutNetError) Error() string { return "timeout" }
|
|
func (timeoutNetError) Timeout() bool { return true }
|
|
func (timeoutNetError) Temporary() bool { return true }
|
|
|
|
func TestNormalizeCardRecognitionCandidates(t *testing.T) {
|
|
got := normalizeCardRecognitionCandidates([]string{
|
|
"ICCID: 8986-0401-7854-7796-048",
|
|
"8986040178547796048",
|
|
"89860401785477960480",
|
|
"7986040178547796048",
|
|
"898604017854779604",
|
|
"898604017854779604800",
|
|
})
|
|
want := []string{
|
|
"8986040178547796048",
|
|
"89860401785477960480",
|
|
}
|
|
if len(got) != len(want) {
|
|
t.Fatalf("len(got) = %d, want %d, got=%v", len(got), len(want), got)
|
|
}
|
|
for index := range want {
|
|
if got[index] != want[index] {
|
|
t.Fatalf("got[%d] = %s, want %s", index, got[index], want[index])
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRequestCardRecognitionModelUsesEinoMultimodalMessage(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
|
|
if request.URL.Path != "/v1/chat/completions" {
|
|
t.Errorf("path = %s, want /v1/chat/completions", request.URL.Path)
|
|
}
|
|
var body struct {
|
|
Messages []struct {
|
|
Content []struct {
|
|
Type string `json:"type"`
|
|
ImageURL struct {
|
|
URL string `json:"url"`
|
|
} `json:"image_url"`
|
|
} `json:"content"`
|
|
} `json:"messages"`
|
|
}
|
|
if err := json.NewDecoder(request.Body).Decode(&body); err != nil {
|
|
t.Errorf("decode request: %v", err)
|
|
}
|
|
if len(body.Messages) != 1 || len(body.Messages[0].Content) != 2 {
|
|
t.Errorf("unexpected messages: %+v", body.Messages)
|
|
} else if !strings.HasPrefix(body.Messages[0].Content[1].ImageURL.URL, "data:image/png;base64,") {
|
|
t.Errorf("image url = %s", body.Messages[0].Content[1].ImageURL.URL)
|
|
}
|
|
writer.Header().Set("Content-Type", "application/json")
|
|
_, _ = writer.Write([]byte(`{"id":"chatcmpl-test","object":"chat.completion","created":1,"model":"test-model","choices":[{"index":0,"message":{"role":"assistant","content":"{\"code\":\"CARD_RECOGNIZED\",\"iccid\":\"8986040178547796048\"}"},"finish_reason":"stop"}]}`))
|
|
}))
|
|
defer server.Close()
|
|
|
|
content, err := requestCardRecognitionModel(context.Background(), cardRecognitionConfig{
|
|
APIKey: "test-key",
|
|
BaseURL: server.URL + "/v1",
|
|
Model: "test-model",
|
|
Timeout: 3 * time.Second,
|
|
MaxTokens: 64,
|
|
}, []byte("image"), "image/png")
|
|
if err != nil {
|
|
t.Fatalf("requestCardRecognitionModel() error = %v", err)
|
|
}
|
|
if !strings.Contains(content, "8986040178547796048") {
|
|
t.Fatalf("content = %s", content)
|
|
}
|
|
}
|
|
|
|
func TestParseCardRecognitionResultMarkdownJSON(t *testing.T) {
|
|
got := parseCardRecognitionResult("```json\n{\"code\":\"CARD_RECOGNIZED\",\"cardType\":\"iccid\",\"iccid\":\"8986040178547796048\",\"candidates\":[\"8986040178547796048\"],\"confidence\":1.2}\n```")
|
|
if got.Code != cardRecognitionCodeRecognized {
|
|
t.Fatalf("Code = %s, want %s", got.Code, cardRecognitionCodeRecognized)
|
|
}
|
|
if got.Confidence != 1 {
|
|
t.Fatalf("Confidence = %v, want 1", got.Confidence)
|
|
}
|
|
if len(got.Candidates) != 1 || got.Candidates[0] != "8986040178547796048" {
|
|
t.Fatalf("Candidates = %v", got.Candidates)
|
|
}
|
|
}
|
|
|
|
func TestParseCardRecognitionResultAmbiguous(t *testing.T) {
|
|
got := parseCardRecognitionResult(`{
|
|
"code": "CARD_AMBIGUOUS",
|
|
"cardType": "iccid",
|
|
"candidates": ["8986040178547796048", "89860401785477960480"],
|
|
"confidence": 0.84
|
|
}`)
|
|
if got.Code != cardRecognitionCodeAmbiguous {
|
|
t.Fatalf("Code = %s, want %s", got.Code, cardRecognitionCodeAmbiguous)
|
|
}
|
|
if len(got.Candidates) != 2 {
|
|
t.Fatalf("Candidates = %v", got.Candidates)
|
|
}
|
|
}
|
|
|
|
func TestParseCardRecognitionResultFiltersInvalidCandidates(t *testing.T) {
|
|
got := parseCardRecognitionResult(`{
|
|
"code": "CARD_RECOGNIZED",
|
|
"iccid": "13800008000",
|
|
"candidates": ["898604017854779604", "898604017854779604800", "7986040178547796048"],
|
|
"confidence": -0.4
|
|
}`)
|
|
if got.Confidence != 0 {
|
|
t.Fatalf("Confidence = %v, want 0", got.Confidence)
|
|
}
|
|
if len(got.Candidates) != 0 {
|
|
t.Fatalf("Candidates = %v, want empty", got.Candidates)
|
|
}
|
|
}
|
|
|
|
func TestParseCardRecognitionResultFallbackFromText(t *testing.T) {
|
|
got := parseCardRecognitionResult("识别到 ICCID 8986040178547796048,另一个候选是 89860401785477960480。")
|
|
if got.Code != cardRecognitionCodeAmbiguous {
|
|
t.Fatalf("Code = %s, want %s", got.Code, cardRecognitionCodeAmbiguous)
|
|
}
|
|
if len(got.Candidates) != 2 {
|
|
t.Fatalf("Candidates = %v", got.Candidates)
|
|
}
|
|
}
|
|
|
|
func TestClampCardRecognitionConfidence(t *testing.T) {
|
|
if got := clampCardRecognitionConfidence(-0.1); got != 0 {
|
|
t.Fatalf("negative clamp = %v, want 0", got)
|
|
}
|
|
if got := clampCardRecognitionConfidence(1.1); got != 1 {
|
|
t.Fatalf("over one clamp = %v, want 1", got)
|
|
}
|
|
if got := clampCardRecognitionConfidence(0.5); got != 0.5 {
|
|
t.Fatalf("normal clamp = %v, want 0.5", got)
|
|
}
|
|
}
|
|
|
|
func TestIsCardRecognitionTimeoutError(t *testing.T) {
|
|
ctx, cancel := context.WithTimeout(context.Background(), time.Nanosecond)
|
|
defer cancel()
|
|
<-ctx.Done()
|
|
if !isCardRecognitionTimeoutError(ctx, errors.New("other")) {
|
|
t.Fatal("deadline exceeded context should be treated as timeout")
|
|
}
|
|
if !isCardRecognitionTimeoutError(context.Background(), timeoutNetError{}) {
|
|
t.Fatal("net timeout error should be treated as timeout")
|
|
}
|
|
if isCardRecognitionTimeoutError(context.Background(), errors.New("temporary unavailable")) {
|
|
t.Fatal("non-timeout error should not be treated as timeout")
|
|
}
|
|
}
|