chore: 初始开源发布
iotlcm-worker-svr:异步执行中枢(RabbitMQ 消费者 + 全量同步编排)。 - Go module:iotlcm-worker-svr → git.5iot.com/iotlcm-public/iotlcm-worker-svr(214 文件) - 凭据与内部标识脱敏:MySQL/Redis 凭据、私网与生产 IP、内网域名、 库名 xiujie_admin → iotlcm、哨兵名占位 - 159 处真实手机号 → 合法 11 位编造号(保留号段与后 4 位,维持互异, 不破坏「校验 MSISDN 格式」等测试断言) - 移除内部资产:CI 触发器、Jenkins 触发测试文档、预演门禁与镜像 digest 记录 - Dockerfile 改用公共基础镜像(golang:1.25-alpine → alpine:3.23), 移除写死的 git 凭据 - README 重写(架构/全量同步模型/配置/已知失败),补 Apache-2.0 LICENSE,LF 行尾 + .gitattributes 已知:3 个测试包在源仓同一提交上同样失败(client/upstream、 service/allocate、service/inbound),属既有缺陷,本仓未引入亦未修复。
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
package contract_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/fullsync/contract"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/queue/zset"
|
||||
)
|
||||
|
||||
// Check every old field, including zero/omitempty behavior, so future adapters
|
||||
// can adopt the contract without silently changing the persisted wire format.
|
||||
func TestLegacyFieldSchemas(t *testing.T) {
|
||||
for _, pair := range []struct{ old, next any }{
|
||||
{zset.RunMeta{}, contract.RunMeta{}},
|
||||
{zset.FailureRecord{}, contract.FailureRecord{}},
|
||||
{zset.Task{}, contract.Task{}},
|
||||
} {
|
||||
old, next := reflect.TypeOf(pair.old), reflect.TypeOf(pair.next)
|
||||
for i := 0; i < old.NumField(); i++ {
|
||||
field := old.Field(i)
|
||||
replacement, ok := next.FieldByName(field.Name)
|
||||
if !ok || replacement.Tag.Get("json") != field.Tag.Get("json") || replacement.Type.Kind() != field.Type.Kind() {
|
||||
t.Errorf("%s.%s lost its legacy schema: old=%+v new=%+v", old.Name(), field.Name, field, replacement)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLegacyRunJSONRoundTrip(t *testing.T) {
|
||||
now := time.Date(2026, 9, 20, 1, 2, 3, 456, time.UTC)
|
||||
legacy := zset.RunMeta{
|
||||
RunID: "legacy", Operation: "current_month_usage_full_sync", QueryMonth: "202609",
|
||||
Status: "completed_with_failures", ProducerDone: true, CurrentRound: 5,
|
||||
SuccessCount: 9, FailedCount: 1, CreatedAt: now, ProducerCompletedAt: now, CompletedAt: now,
|
||||
}
|
||||
data := mustJSON(t, legacy)
|
||||
var decoded contract.RunMeta
|
||||
if err := json.Unmarshal(data, &decoded); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := decoded.Validate(); err != nil {
|
||||
t.Fatalf("legacy run rejected: %v", err)
|
||||
}
|
||||
if got := string(mustJSON(t, decoded)); got != string(data) {
|
||||
t.Fatalf("legacy wire format changed:\n%s\n%s", data, got)
|
||||
}
|
||||
// New fields do not prevent old readers from consuming the same metadata.
|
||||
decoded.SyncType = contract.SyncReplay
|
||||
decoded.SourceRunID = "source"
|
||||
decoded.ReplayCycle = 1
|
||||
decoded.EnqueuedCount = 10
|
||||
decoded.PendingFailureCount = 1
|
||||
var oldReader zset.RunMeta
|
||||
if err := json.Unmarshal(mustJSON(t, decoded), &oldReader); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
oldReader.SyncType = ""
|
||||
oldReader.SourceRunID = ""
|
||||
oldReader.ReplayCycle = 0
|
||||
oldReader.EnqueuedCount = 0
|
||||
oldReader.PendingFailureCount = 0
|
||||
if !reflect.DeepEqual(oldReader, legacy) {
|
||||
t.Fatalf("old reader lost fields: %+v", oldReader)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLegacyFailureAndTaskJSON(t *testing.T) {
|
||||
legacy := zset.FailureRecord{
|
||||
Version: 1, TaskID: "source:op:42", OriginalRunID: "source", LatestRunID: "latest",
|
||||
QueryMonth: "202609", Operation: "op", CardID: 42, ICCID: "iccid", MSISDN: "msisdn", IMSI: "imsi",
|
||||
ChanKey: "chan", TempKey: "template", Carrier: "mobile", FailureType: "mobile_busy_exhausted",
|
||||
Round: 5, Attempts: 7, Code: 13000, ErrCode: "BUSY", Message: "try again",
|
||||
ReviewStatus: "pending", ReviewRemark: "remark", ReviewedBy: "operator", ReviewedAt: "2026-09-20",
|
||||
RetryCycle: 2, ReplayRunID: "replayed", FirstFailedAt: time.Unix(1, 0).UTC(), LastFailedAt: time.Unix(2, 0).UTC(),
|
||||
}
|
||||
var record contract.FailureRecord
|
||||
if err := json.Unmarshal(mustJSON(t, legacy), &record); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(mustJSON(t, record)) != string(mustJSON(t, legacy)) {
|
||||
t.Fatal("failure record wire format changed")
|
||||
}
|
||||
if summary := record.Summary(); summary.ErrorCategory != contract.ErrorRateLimited || summary.TaskID != legacy.TaskID || summary.FailedAt != legacy.LastFailedAt {
|
||||
t.Fatalf("incorrect failure projection: %+v", summary)
|
||||
}
|
||||
task := zset.Task{TaskID: "r:op:42", RunID: "r", Operation: "op", QueryMonth: "202609", ChanKey: "c", TempKey: "t", Operator: 2, CardID: 42, ICCID: "i", MSISDN: "m", IMSI: "s", NextExecuteAt: 1234}
|
||||
var newTask contract.Task
|
||||
if err := json.Unmarshal(mustJSON(t, task), &newTask); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
newTask.NextExecuteAt = 9999
|
||||
if string(mustJSON(t, newTask)) != string(mustJSON(t, task)) {
|
||||
t.Fatal("task wire format or scheduling exclusion changed")
|
||||
}
|
||||
}
|
||||
|
||||
func mustJSON(t *testing.T, value any) []byte {
|
||||
t.Helper()
|
||||
data, err := json.Marshal(value)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return data
|
||||
}
|
||||
Reference in New Issue
Block a user