2bec257289
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),属既有缺陷,本仓未引入亦未修复。
104 lines
4.5 KiB
Go
104 lines
4.5 KiB
Go
package contract_test
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/fullsync/contract"
|
|
)
|
|
|
|
func TestReplayRequestIdentity(t *testing.T) {
|
|
request := contract.ReplayRequest{SourceRunID: "source", IdempotencyKey: "key", RequestedBy: "operator"}
|
|
fingerprint, err := request.Fingerprint()
|
|
if err != nil || len(fingerprint) != 64 {
|
|
t.Fatalf("fingerprint=%q, err=%v", fingerprint, err)
|
|
}
|
|
for name, mutate := range map[string]func(*contract.ReplayRequest){
|
|
"same": func(*contract.ReplayRequest) {},
|
|
"key": func(r *contract.ReplayRequest) { r.IdempotencyKey = "another-key" },
|
|
"source": func(r *contract.ReplayRequest) { r.SourceRunID = "another-source" },
|
|
"body": func(r *contract.ReplayRequest) { r.RequestedBy = "another-operator" },
|
|
} {
|
|
copy := request
|
|
mutate(©)
|
|
got, err := copy.Fingerprint()
|
|
if err != nil || (got == fingerprint) != (name == "same" || name == "key") {
|
|
t.Errorf("%s fingerprint=%s err=%v", name, got, err)
|
|
}
|
|
}
|
|
if got := string(mustJSON(t, request)); got != `{"requestedBy":"operator"}` {
|
|
t.Fatalf("path/header leaked into body: %s", got)
|
|
}
|
|
for _, bad := range []contract.ReplayRequest{
|
|
{SourceRunID: "source", RequestedBy: "operator"},
|
|
{SourceRunID: "source", IdempotencyKey: "key", RequestedBy: " "},
|
|
{SourceRunID: "{source}", IdempotencyKey: "key", RequestedBy: "operator"},
|
|
{SourceRunID: "source", IdempotencyKey: "key\r\ninjected", RequestedBy: "operator"},
|
|
{SourceRunID: "source", IdempotencyKey: strings.Repeat("x", 257), RequestedBy: "operator"},
|
|
} {
|
|
if _, err := bad.Fingerprint(); !errors.Is(err, contract.ErrInvalidArgument) {
|
|
t.Errorf("bad request returned %v", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestActionProgressAndReceiptValidation(t *testing.T) {
|
|
for _, progress := range []contract.ReplayProgress{
|
|
{DiscoveredCount: -1}, {DiscoveredCount: 1, EnqueuedCount: 2},
|
|
{DiscoveredCount: 2, EnqueuedCount: 1, CleanedCount: 2}, {FailureCount: -1},
|
|
} {
|
|
if err := progress.Validate(); !errors.Is(err, contract.ErrInvalidArgument) {
|
|
t.Fatalf("bad progress %+v returned %v", progress, err)
|
|
}
|
|
}
|
|
now := time.Now().UTC()
|
|
action := contract.ReplayAction{ActionID: "action", SourceRunID: "source", TargetRunID: "target", RequestedBy: "operator", ReplayCycle: 1, Status: contract.ActionAccepted}
|
|
if err := action.Validate(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
action.Status = contract.ActionCompleted
|
|
if err := action.Validate(); !errors.Is(err, contract.ErrInvalidArgument) {
|
|
t.Fatalf("completed action without receipt returned %v", err)
|
|
}
|
|
action.Receipt = &contract.ArchiveReceipt{Version: 1, Path: "receipt.json", Checksum: strings.Repeat("a", 64), ArchivedAt: now}
|
|
action.CompletedAt = &now
|
|
if err := action.Validate(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
action.Receipt.Checksum = "not-a-checksum"
|
|
if err := action.Validate(); !errors.Is(err, contract.ErrInvalidArgument) {
|
|
t.Fatalf("bad receipt returned %v", err)
|
|
}
|
|
}
|
|
|
|
func TestActionWireFormatHidesInternalState(t *testing.T) {
|
|
action := contract.ReplayAction{
|
|
ActionID: "action", SourceRunID: "source", TargetRunID: "target", RequestedBy: "operator", ReplayCycle: 1,
|
|
Status: contract.ActionCleanupPending, IdempotencyKey: "secret", Fingerprint: "internal-fingerprint",
|
|
LeaseOwner: "worker", LeaseToken: "token", LeaseUntil: time.Now(), Revision: 9,
|
|
Progress: contract.ReplayProgress{ScanCursor: "opaque", DiscoveredCount: 10, EnqueuedCount: 8, CleanedCount: 7, FailureCount: 2},
|
|
}
|
|
data := mustJSON(t, action)
|
|
for _, hidden := range []string{"idempotency", "fingerprint", "lease", "revision", "secret", "token"} {
|
|
if strings.Contains(strings.ToLower(string(data)), hidden) {
|
|
t.Errorf("internal %s leaked: %s", hidden, data)
|
|
}
|
|
}
|
|
var decoded contract.ReplayAction
|
|
if err := json.Unmarshal(data, &decoded); err != nil || !reflect.DeepEqual(decoded.Progress, action.Progress) || decoded.Status != contract.ActionCleanupPending {
|
|
t.Fatalf("action roundtrip lost state: %+v, %v", decoded, err)
|
|
}
|
|
statusURL, err := contract.ActionStatusURL("action%1")
|
|
if err != nil || statusURL != "/internal/v1/full-sync-actions/action%251" {
|
|
t.Fatalf("status URL=%s, %v", statusURL, err)
|
|
}
|
|
accepted := contract.ReplayAccepted{ActionID: "action", TargetRunID: "target", Status: contract.ActionAccepted, StatusURL: "/internal/v1/full-sync-actions/action"}
|
|
if got := string(mustJSON(t, accepted)); got != `{"actionId":"action","targetRunId":"target","status":"accepted","statusUrl":"/internal/v1/full-sync-actions/action"}` {
|
|
t.Fatalf("202 contract changed: %s", got)
|
|
}
|
|
}
|