- 新增存储空间管理:会员套餐配置、用户容量/到期管理、后台支付记录查询 - 接入个人主体小程序虚拟支付:下单/发货推送/兜底查单(query_order) - 修复 adminStorageUsers 等接口鉴权、out_trade_no 长度、前端 requestVirtualPayment 参数 - 前端上传前容量预检,超容引导升级无限空间 - 我的页会员卡片展示「无限空间」与已用容量/到期时间 - 后台「存储空间管理」新增支付记录标签页
143 lines
6.6 KiB
Go
143 lines
6.6 KiB
Go
package wechat
|
|
|
|
import (
|
|
"database/sql/driver"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"past/internal/store"
|
|
"past/internal/testdb"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
type subscribeTransport func(*http.Request) (*http.Response, error)
|
|
|
|
func (f subscribeTransport) RoundTrip(r *http.Request) (*http.Response, error) { return f(r) }
|
|
|
|
func TestSubscribeRecordsEveryOutcome(t *testing.T) {
|
|
for _, tt := range []struct {
|
|
name, openid, template, response, reason string
|
|
subscribed, success bool
|
|
code any
|
|
requests int
|
|
}{
|
|
{name: "success", openid: "recipient-openid", template: "template-id", subscribed: true, response: `{"errcode":0}`, success: true, code: int64(0), requests: 1},
|
|
{name: "quota exhausted", openid: "recipient-openid", template: "template-id", subscribed: true, response: `{"errcode":43101,"errmsg":"user refuse"}`, reason: "43101", code: int64(43101), requests: 1},
|
|
{name: "malformed response", openid: "recipient-openid", template: "template-id", subscribed: true, response: `not-json`, reason: "无效", requests: 1},
|
|
{name: "invalid errcode", openid: "recipient-openid", template: "template-id", subscribed: true, response: `{"errcode":"invalid"}`, reason: "无效", requests: 1},
|
|
{name: "missing errcode", openid: "recipient-openid", template: "template-id", subscribed: true, response: `{}`, reason: "errcode", requests: 1},
|
|
{name: "no authorization", openid: "recipient-openid", template: "template-id", reason: "未订阅"},
|
|
{name: "no openid", template: "template-id", reason: "openid"},
|
|
{name: "no template", openid: "recipient-openid", reason: "模板未配置"},
|
|
} {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
steps := []testdb.Step{{Match: "SELECT status FROM notify_log", Columns: []string{"status"}}, {Match: "SELECT openid FROM users", Args: []any{int64(2)}, Columns: []string{"openid"}, Rows: [][]driver.Value{{tt.openid}}}}
|
|
if tt.openid != "" && tt.template != "" {
|
|
auth := [][]driver.Value(nil)
|
|
if tt.subscribed {
|
|
auth = [][]driver.Value{{int64(1)}}
|
|
}
|
|
steps = append(steps, testdb.Step{Match: "FROM subscribe_templates", Args: []any{int64(2), "notify"}, Columns: []string{"id"}, Rows: auth})
|
|
}
|
|
meta := []testdb.Step{{Match: "SELECT couple_id FROM users", Columns: []string{"couple_id"}, Rows: [][]driver.Value{{int64(5)}}}, {Match: "SELECT action_type FROM activity_log", Columns: []string{"action_type"}, Rows: [][]driver.Value{{"diary_create"}}}}
|
|
steps = append(steps, meta...)
|
|
steps = append(steps, testdb.Step{Match: "INSERT INTO subscribe_message_logs", Check: func(q string, args []any) error {
|
|
if args[4] != tt.openid || args[5] != "diary_create" || args[6] != "new diary" {
|
|
t.Errorf("wrong recipient/event: %v", args)
|
|
return nil
|
|
}
|
|
if args[7] != strings.Repeat("爱", 20) {
|
|
t.Errorf("log must record normalized content: %v", args[7])
|
|
}
|
|
status := "failed"
|
|
if tt.success {
|
|
status = "success"
|
|
}
|
|
if args[11] != status || args[12] != tt.code || args[15] != int64(tt.requests) {
|
|
t.Errorf("wrong result: %v", args)
|
|
return nil
|
|
}
|
|
if !strings.Contains(fmt.Sprint(args[14]), tt.reason) {
|
|
t.Errorf("missing failure reason: %v", args[14])
|
|
return nil
|
|
}
|
|
if tt.success && args[17] == nil {
|
|
t.Error("missing sent_at")
|
|
return nil
|
|
}
|
|
if !tt.success && args[17] != nil {
|
|
t.Error("failed message has sent_at")
|
|
return nil
|
|
}
|
|
return nil
|
|
}})
|
|
steps = append(steps, meta...)
|
|
steps = append(steps, testdb.Step{Match: "SELECT COUNT(*) as cnt FROM notify_log", Columns: []string{"cnt"}, Rows: [][]driver.Value{{int64(0)}}}, testdb.Step{Match: "INSERT INTO notify_log"})
|
|
db, _ := testdb.Open(t, steps)
|
|
client := New("app", "secret", tt.template, t.TempDir(), &store.DB{DB: db})
|
|
os.MkdirAll(filepath.Dir(client.cacheFile()), 0755)
|
|
os.WriteFile(client.cacheFile(), []byte("cached-token"), 0600)
|
|
calls := 0
|
|
client.http.Transport = subscribeTransport(func(r *http.Request) (*http.Response, error) {
|
|
calls++
|
|
var payload map[string]any
|
|
json.NewDecoder(r.Body).Decode(&payload)
|
|
if payload["touser"] != tt.openid {
|
|
t.Fatal("wrong WeChat recipient")
|
|
}
|
|
if payload["data"].(map[string]any)["thing2"].(map[string]any)["value"] != strings.Repeat("爱", 20) {
|
|
t.Fatal("overlong content sent to WeChat")
|
|
}
|
|
return &http.Response{StatusCode: 200, Body: io.NopCloser(strings.NewReader(tt.response))}, nil
|
|
})
|
|
success := client.SendSubscribeMessage(2, "notify", map[string]string{"thing1": "new diary", "thing2": strings.Repeat("爱", 50)}, "", 10, 1)
|
|
if success != tt.success || calls != tt.requests {
|
|
t.Fatalf("success=%v requests=%d", success, calls)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSubscribeWithoutActivityStillRecords(t *testing.T) {
|
|
db, _ := testdb.Open(t, []testdb.Step{
|
|
{Match: "SELECT openid FROM users", Columns: []string{"openid"}, Rows: [][]driver.Value{{"recipient"}}},
|
|
{Match: "SELECT id FROM subscribe_templates", Columns: []string{"id"}},
|
|
{Match: "SELECT couple_id FROM users", Columns: []string{"couple_id"}, Rows: [][]driver.Value{{nil}}},
|
|
{Match: "INSERT INTO subscribe_message_logs", Check: func(_ string, args []any) error {
|
|
if args[0] != int64(0) || args[4] != "recipient" || args[11] != "failed" {
|
|
t.Error("missing zero-activity log")
|
|
return nil
|
|
}
|
|
return nil
|
|
}},
|
|
})
|
|
client := New("app", "secret", "template", t.TempDir(), &store.DB{DB: db})
|
|
if client.SendSubscribeMessage(2, "notify", nil, "", 0, 0) {
|
|
t.Fatal("unsubscribed send succeeded")
|
|
}
|
|
}
|
|
|
|
func TestSuccessfulEventIsNotSentAgain(t *testing.T) {
|
|
db, _ := testdb.Open(t, []testdb.Step{{Match: "SELECT status FROM notify_log", Columns: []string{"status"}, Rows: [][]driver.Value{{"success"}}}})
|
|
client := New("app", "secret", "template", t.TempDir(), &store.DB{DB: db})
|
|
client.http.Transport = subscribeTransport(func(*http.Request) (*http.Response, error) { t.Fatal("duplicate sent to WeChat"); return nil, nil })
|
|
if !client.SendSubscribeMessage(2, "notify", nil, "", 10, 1) {
|
|
t.Fatal("completed message must be treated as success")
|
|
}
|
|
}
|
|
|
|
func TestThingNormalizationDoesNotMutateInput(t *testing.T) {
|
|
input := map[string]string{"thing1": strings.Repeat("爱💕", 30), "thing2": " ", "thing4": "行一\n行二", "date3": "2026-10-11"}
|
|
output := normalizeSubscribeData(input)
|
|
if len([]rune(output["thing1"])) != 20 || output["thing2"] == "" || output["thing4"] != "行一 行二" || output["date3"] != input["date3"] {
|
|
t.Fatalf("wrong normalized fields: %v", output)
|
|
}
|
|
if output["thing1"] == input["thing1"] || input["thing2"] != " " {
|
|
t.Fatal("input was mutated")
|
|
}
|
|
}
|