Files

221 lines
8.7 KiB
Go

package simwarning
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gcfg"
)
func TestMonthlyExpiryRunPagesAndPartialFailures(t *testing.T) {
firstPage := make([]monthlyExpiryCompany, monthlyExpiryPageSize)
for i := range firstPage {
firstPage[i] = monthlyExpiryCompany{TenantID: "000000", DeptID: int64(i + 1), LeaderID: int64(i + 1001)}
}
calls := make(map[string]int)
pages := make([]int64, 0, 2)
runner := monthlyExpiryRunner{
companies: func(_ context.Context, lastID int64) ([]monthlyExpiryCompany, error) {
pages = append(pages, lastID)
if lastID == 0 {
return firstPage, nil
}
if lastID == int64(monthlyExpiryPageSize) {
return []monthlyExpiryCompany{{TenantID: "000000", DeptID: 101, LeaderID: 1101}}, nil
}
return nil, fmt.Errorf("unexpected cursor %d", lastID)
},
summaries: func(_ context.Context, company monthlyExpiryCompany, begin, end time.Time) ([]monthlyExpirySummary, error) {
if begin.Format("2006-01-02") != "2026-12-01" || end.Format("2006-01-02") != "2027-02-01" {
t.Errorf("wrong query window %s..%s", begin, end)
}
if company.DeptID == 2 {
return nil, errors.New("count failed")
}
return []monthlyExpirySummary{{Month: "2026-12", Count: 3, FirstDate: "2026-12-01", LastDate: "2026-12-31"},
{Month: "2027-01", Count: 6, FirstDate: "2027-01-31", LastDate: "2027-01-31"}}, nil
},
leader: func(_ context.Context, company monthlyExpiryCompany) (*monthlyExpiryLeader, error) {
if company.DeptID == 3 {
return nil, nil
}
return &monthlyExpiryLeader{UserID: company.LeaderID, Phone: "13800000000"}, nil
},
deliver: func(_ context.Context, notice monthlyExpiryNotice, channel string) (bool, error) {
calls[notice.Summary.Month+"/"+channel]++
if notice.Company.DeptID == 1 && channel == "sms" {
return false, errors.New("sms failed")
}
if notice.Company.DeptID == 1 && notice.Summary.Month == "2027-01" && channel == "platform" {
return false, nil
}
if notice.Company.DeptID == 4 && notice.Summary.Month == "2026-12" && channel == "platform" {
return true, errors.New("dedupe mark failed after delivery")
}
return true, nil
},
}
stats, err := runner.run(context.Background(), time.Date(2026, 12, 15, 9, 0, 0, 0, monthlyExpiryLocation))
if err != nil {
t.Fatal(err)
}
if stats.Companies != 101 || stats.Groups != 198 || stats.Sent != 393 || stats.Duplicate != 1 || stats.Errors != 5 {
t.Fatalf("unexpected stats: %+v", stats)
}
if len(pages) != 2 || pages[0] != 0 || pages[1] != 100 {
t.Fatalf("wrong page cursors: %v", pages)
}
if calls["2026-12/platform"] != 99 || calls["2027-01/platform"] != 99 {
t.Fatalf("wrong delivered group counts: %+v", calls)
}
}
func TestMonthlyExpiryNoticeContentAndRecipient(t *testing.T) {
base := monthlyExpiryNotice{
Company: monthlyExpiryCompany{TenantID: "000000", DeptID: 117, LeaderID: 12},
Leader: monthlyExpiryLeader{UserID: 12},
RunTime: time.Date(2026, 10, 15, 9, 0, 0, 0, monthlyExpiryLocation),
Summary: monthlyExpirySummary{Month: "2026-10", Count: 7, FirstDate: "2026-10-21", LastDate: "2026-10-31"},
}
current := base.content()
for _, text := range []string{"【7】张", "2026-10-21至2026-10-31", "本月25日前", "本月底卡片将执行停机"} {
if !strings.Contains(current, text) {
t.Errorf("current notice missing %q: %s", text, current)
}
}
param := monthlyExpiryMessageParam(base)
if *param.MessageType != 1 || *param.SendUserType != 1 || *param.MessageTitle != "到期提醒" || !strings.Contains(param.UserIds.String(), "12") {
t.Fatalf("wrong targeted system message: %+v", param)
}
base.Summary = monthlyExpirySummary{Month: "2026-11", Count: 9, FirstDate: "2026-11-30", LastDate: "2026-11-30"}
if got := base.content(); !strings.Contains(got, "次月25日前") || strings.Contains(got, "本月底卡片") {
t.Fatalf("next month notice incorrect: %s", got)
}
if base.expiryDateText() != "2026-11-30" {
t.Errorf("single-day range not collapsed: %s", base.expiryDateText())
}
if key := monthlyExpirySentKey(base, "sms"); !strings.Contains(key, ":000000:117:2026-11:sms") {
t.Errorf("missing dedupe dimensions: %s", key)
}
}
func TestMonthlyExpirySMSParamsRequireConfirmedTemplate(t *testing.T) {
for _, invalid := range []string{"", "invalid", "{}", `{"count":7}`} {
if _, err := renderMonthlyExpirySMSParams(invalid, 3, "2026-11-30"); err == nil {
t.Errorf("unconfirmed/invalid template accepted: %q", invalid)
}
}
content, err := renderMonthlyExpirySMSParams(`{"actualCount":"{count}","actualExpire":"{expireDate}"}`, 5, "2026-11-01至2026-11-30")
if err != nil {
t.Fatal(err)
}
var params map[string]string
if err := json.Unmarshal([]byte(content), &params); err != nil || params["actualCount"] != "5" || params["actualExpire"] != "2026-11-01至2026-11-30" {
t.Fatalf("incorrect template params: %s, %v", content, err)
}
}
func TestMonthlyExpirySMSLocalGateway(t *testing.T) {
// 所有请求只发到本机测试服务;清除环境覆盖,绝不读取真实网关配置。
for _, name := range []string{"IOTLCM_SMS_GATEWAY_ENABLED", "IOTLCM_SMS_GATEWAY_URL", "IOTLCM_SMS_GATEWAY_GRPC_ADDR", "IOTLCM_SMS_GATEWAY_TIMEOUT", "IOTLCM_SMS_GATEWAY_ROUTER_ID"} {
t.Setenv(name, "")
}
requests := make([]map[string]map[string]string, 0, 3)
fail := false
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var body map[string]map[string]string
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
t.Errorf("decode request: %v", err)
}
requests = append(requests, body)
if fail {
_, _ = w.Write([]byte(`{"code":502,"message":"supplier failure"}`))
return
}
_, _ = w.Write([]byte(`{"code":0,"message":"OK"}`))
}))
defer server.Close()
adapter, err := gcfg.NewAdapterContent()
if err != nil {
t.Fatal(err)
}
original := g.Cfg().GetAdapter()
g.Cfg().SetAdapter(adapter)
t.Cleanup(func() { g.Cfg().SetAdapter(original) })
configure := func(enabled bool, template string) {
t.Helper()
data, err := json.Marshal(g.Map{
"sms": g.Map{"gateway": g.Map{"enabled": enabled, "url": server.URL, "timeout": "1s"}},
"warning": g.Map{"expire": g.Map{"monthly": g.Map{
"currentMonthParamsTemplate": template, "nextMonthParamsTemplate": template,
}}},
})
if err != nil {
t.Fatal(err)
}
if err := adapter.SetContent(string(data)); err != nil {
t.Fatal(err)
}
}
configure(true, `{"card_num":"{count}","expire_date":"{expireDate}"}`)
notice := monthlyExpiryNotice{
RunTime: time.Date(2026, 10, 15, 9, 0, 0, 0, monthlyExpiryLocation),
Leader: monthlyExpiryLeader{UserID: 12, Phone: "13800000000"},
Summary: monthlyExpirySummary{Month: "2026-10", Count: 7, FirstDate: "2026-10-31", LastDate: "2026-10-31"},
}
if err := sendMonthlyExpirySMS(context.Background(), notice); err != nil {
t.Fatal(err)
}
notice.Summary = monthlyExpirySummary{Month: "2026-11", Count: 9, FirstDate: "2026-11-01", LastDate: "2026-11-30"}
if err := sendMonthlyExpirySMS(context.Background(), notice); err != nil {
t.Fatal(err)
}
if len(requests) != 2 || requests[0]["config"]["templateId"] != "SMS_512596191" || requests[1]["config"]["templateId"] != "SMS_512646156" {
t.Fatalf("incorrect gateway templates: %+v", requests)
}
for _, request := range requests {
if request["config"]["signName"] != "示例企业" {
t.Fatalf("incorrect gateway sign: %+v", request)
}
}
var params map[string]string
if err := json.Unmarshal([]byte(requests[1]["params"]["msgContent"]), &params); err != nil || params["card_num"] != "9" || params["expire_date"] != "2026-11-01至2026-11-30" {
t.Fatalf("incorrect gateway params: %+v, %v", params, err)
}
fail = true
if err := sendMonthlyExpirySMS(context.Background(), notice); err == nil {
t.Fatal("gateway business failure must propagate")
}
configure(true, "")
if err := sendMonthlyExpirySMS(context.Background(), notice); err == nil || len(requests) != 3 {
t.Fatal("unconfirmed template must block before calling gateway")
}
configure(false, `{"card_num":"{count}","expire_date":"{expireDate}"}`)
if err := sendMonthlyExpirySMS(context.Background(), notice); err == nil || len(requests) != 3 {
t.Fatal("disabled gateway must not count as successful delivery")
}
}
func TestMonthlyExpiryCancellationStopsBeforeDelivery(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
cancel()
runner := monthlyExpiryRunner{
companies: func(context.Context, int64) ([]monthlyExpiryCompany, error) {
t.Fatal("canceled run should not query")
return nil, nil
},
}
_, err := runner.run(ctx, time.Now())
if !errors.Is(err, context.Canceled) {
t.Fatalf("expected cancellation, got %v", err)
}
}