Use UUID models and soft deletion, persist file relationships with deduplicated monthly MinIO objects, and add admin file management. Update mini-program media handling and add local dev configuration.
146 lines
7.0 KiB
Go
146 lines
7.0 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 {
|
|
v := testdb.InsertArgs(q, args)
|
|
args = []any{v["activity_id"], v["couple_id"], v["user_id"], v["to_user_id"], v["to_openid"], v["action_type"], v["message_title"], v["message_content"], v["template_type"], v["template_id"], v["page"], v["status"], v["error_code"], v["http_status"], v["error_msg"], v["attempts"], v["duration_ms"], v["sent_at"]}
|
|
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.WrapSQL(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(q string, args []any) error {
|
|
v := testdb.InsertArgs(q, args)
|
|
if v["activity_id"] != nil || v["to_openid"] != "recipient" || v["status"] != "failed" {
|
|
t.Error("missing zero-activity log")
|
|
}
|
|
|
|
return nil
|
|
}},
|
|
})
|
|
client := New("app", "secret", "template", t.TempDir(), store.WrapSQL(db))
|
|
if client.SendSubscribeMessage("2", "notify", nil, "", "", "") {
|
|
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.WrapSQL(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")
|
|
}
|
|
}
|