feat: complete cmpp platform phases 0-5
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
package spike
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"cmpp-platform/gateway/internal/queue"
|
||||
"cmpp-platform/gateway/internal/tracker"
|
||||
)
|
||||
|
||||
type Simulator struct {
|
||||
tracker *tracker.Tracker
|
||||
sequence atomic.Uint32
|
||||
}
|
||||
|
||||
type RunResult struct {
|
||||
Submitted int
|
||||
SubmitResults int
|
||||
ReceiptEvents int
|
||||
Duration time.Duration
|
||||
MessagesPerSec float64
|
||||
}
|
||||
|
||||
func NewSimulator() *Simulator {
|
||||
return &Simulator{tracker: tracker.New()}
|
||||
}
|
||||
|
||||
func (s *Simulator) Submit(ctx context.Context, cmd queue.SubmitCommand) (queue.SubmitResult, queue.ReceiptEvent, error) {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return queue.SubmitResult{}, queue.ReceiptEvent{}, err
|
||||
}
|
||||
|
||||
sequenceID := s.sequence.Add(1)
|
||||
s.tracker.TrackSubmit(cmd.MessageID, sequenceID)
|
||||
|
||||
gatewayMessageID := fmt.Sprintf("gw-%s", cmd.MessageID)
|
||||
mapping, err := s.tracker.TrackSubmitResp(sequenceID, gatewayMessageID)
|
||||
if err != nil {
|
||||
return queue.SubmitResult{}, queue.ReceiptEvent{}, err
|
||||
}
|
||||
|
||||
now := time.Now().UTC()
|
||||
result := queue.SubmitResult{
|
||||
Envelope: queue.Envelope{
|
||||
SchemaVersion: queue.SchemaVersion,
|
||||
MessageType: queue.MessageTypeSubmitResult,
|
||||
TraceID: cmd.TraceID,
|
||||
MessageID: mapping.MessageID,
|
||||
ChannelID: cmd.ChannelID,
|
||||
CreatedAt: now,
|
||||
},
|
||||
SequenceID: mapping.SequenceID,
|
||||
GatewayMessageID: mapping.GatewayMessageID,
|
||||
SubmitStatus: "accepted",
|
||||
SubmittedAt: now,
|
||||
}
|
||||
|
||||
receipt := queue.ReceiptEvent{
|
||||
Envelope: queue.Envelope{
|
||||
SchemaVersion: queue.SchemaVersion,
|
||||
MessageType: queue.MessageTypeReceiptEvent,
|
||||
TraceID: cmd.TraceID,
|
||||
MessageID: mapping.MessageID,
|
||||
ChannelID: cmd.ChannelID,
|
||||
CreatedAt: now.Add(10 * time.Millisecond),
|
||||
},
|
||||
SequenceID: mapping.SequenceID,
|
||||
GatewayMessageID: mapping.GatewayMessageID,
|
||||
ReceiptStatus: "delivered",
|
||||
RawStatus: "DELIVRD",
|
||||
DeliveredAt: now.Add(10 * time.Millisecond),
|
||||
}
|
||||
|
||||
return result, receipt, nil
|
||||
}
|
||||
|
||||
func (s *Simulator) RunLoad(ctx context.Context, count int) (RunResult, error) {
|
||||
start := time.Now()
|
||||
result := RunResult{Submitted: count}
|
||||
|
||||
for i := 0; i < count; i++ {
|
||||
cmd := NewSubmitCommand(i)
|
||||
_, _, err := s.Submit(ctx, cmd)
|
||||
if err != nil {
|
||||
return RunResult{}, err
|
||||
}
|
||||
result.SubmitResults++
|
||||
result.ReceiptEvents++
|
||||
}
|
||||
|
||||
result.Duration = time.Since(start)
|
||||
if result.Duration > 0 {
|
||||
result.MessagesPerSec = float64(count) / result.Duration.Seconds()
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func NewSubmitCommand(index int) queue.SubmitCommand {
|
||||
now := time.Now().UTC()
|
||||
messageID := fmt.Sprintf("msg-spike-%06d", index)
|
||||
return queue.SubmitCommand{
|
||||
Envelope: queue.Envelope{
|
||||
SchemaVersion: queue.SchemaVersion,
|
||||
MessageType: queue.MessageTypeSubmitCommand,
|
||||
TraceID: fmt.Sprintf("trace-spike-%06d", index),
|
||||
MessageID: messageID,
|
||||
ChannelID: "sms-channel-cmpp-spike",
|
||||
CreatedAt: now,
|
||||
},
|
||||
TenantID: "tenant-spike",
|
||||
ApplicationID: "app-spike",
|
||||
TaskID: "task-spike",
|
||||
SubmitID: fmt.Sprintf("submit-spike-%06d", index),
|
||||
PhoneNumber: "13800138000",
|
||||
Content: "您的验证码为 123456,5 分钟内有效。",
|
||||
Signature: "测试平台",
|
||||
TemplateID: "tpl-spike",
|
||||
BillingUnits: 1,
|
||||
Route: queue.Route{
|
||||
ChannelCode: "CMCC-CMPP-SPIKE",
|
||||
CMPPAccountCode: "cmpp-account-spike",
|
||||
Priority: 10,
|
||||
RateLimitPerSecond: 500,
|
||||
},
|
||||
CMPP: queue.CMPP{
|
||||
ServiceID: "CMPP",
|
||||
SrcID: "106900000000",
|
||||
RegisteredDelivery: 1,
|
||||
MsgFmt: 15,
|
||||
FeeUserType: 2,
|
||||
FeeCode: "0",
|
||||
FeeType: "01",
|
||||
},
|
||||
Retry: queue.Retry{Attempt: 0, MaxAttempts: 3},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package spike
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestSimulatorRunsOneMessageLifecycle(t *testing.T) {
|
||||
sim := NewSimulator()
|
||||
cmd := NewSubmitCommand(1)
|
||||
|
||||
result, receipt, err := sim.Submit(context.Background(), cmd)
|
||||
if err != nil {
|
||||
t.Fatalf("submit: %v", err)
|
||||
}
|
||||
|
||||
if result.MessageID != cmd.MessageID {
|
||||
t.Fatalf("submit result message id mismatch: %s != %s", result.MessageID, cmd.MessageID)
|
||||
}
|
||||
if result.SequenceID == 0 {
|
||||
t.Fatal("expected sequence id")
|
||||
}
|
||||
if receipt.GatewayMessageID != result.GatewayMessageID {
|
||||
t.Fatalf("receipt gateway id mismatch: %s != %s", receipt.GatewayMessageID, result.GatewayMessageID)
|
||||
}
|
||||
if receipt.ReceiptStatus != "delivered" {
|
||||
t.Fatalf("unexpected receipt status: %s", receipt.ReceiptStatus)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSimulatorLoadMeets500TPSFloor(t *testing.T) {
|
||||
sim := NewSimulator()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
result, err := sim.RunLoad(ctx, 15000)
|
||||
if err != nil {
|
||||
t.Fatalf("run load: %v", err)
|
||||
}
|
||||
|
||||
if result.SubmitResults != 15000 || result.ReceiptEvents != 15000 {
|
||||
t.Fatalf("unexpected result counts: %+v", result)
|
||||
}
|
||||
if result.MessagesPerSec < 500 {
|
||||
t.Fatalf("expected at least 500 msg/s, got %.2f", result.MessagesPerSec)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user