Deploy MQTT test backend with private viewer and Gitea CI/CD
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
package backend
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/eclipse/paho.golang/paho"
|
||||
"robot-market/test-mqtt-5/internal/events"
|
||||
)
|
||||
|
||||
func TestReceiptRawPayloadRoutingAndCorrelation(t *testing.T) {
|
||||
b := testBackend()
|
||||
b.Client.Config.Topics.ObserveAll = true
|
||||
for _, payload := range []string{"hello", "", "[1,2]", `{"device_id":"different","timestamp":"{{$isoTimestamp}}","message_id":"msg-1"}`} {
|
||||
reply := b.receipt(&paho.Publish{Topic: "robot-market/test/v1/real-device/telemetry", Payload: []byte(payload)})
|
||||
if reply == nil || reply.Topic != "robot-market/test/v1/real-device/acks" || reply.Retain || reply.QoS != 1 {
|
||||
t.Fatal("missing/routed/retained reply")
|
||||
}
|
||||
var ack ReceiptAck
|
||||
if json.Unmarshal(reply.Payload, &ack) != nil || !ack.OK || ack.Type != "receipt_ack" || ack.PayloadBytes != len(payload) || len(ack.PayloadSHA256) != 64 {
|
||||
t.Fatal("invalid receipt")
|
||||
}
|
||||
}
|
||||
p := &paho.Publish{Topic: "robot-market/test/v1/device/telemetry", Payload: []byte("x"), Properties: &paho.PublishProperties{ResponseTopic: "robot-market/test/v1/replies/custom", CorrelationData: []byte{0, 255, 1}}}
|
||||
reply := b.receipt(p)
|
||||
p.Properties.CorrelationData[0] = 99
|
||||
if reply.Topic != "robot-market/test/v1/replies/custom" || reply.Properties.CorrelationData[0] != 0 || reply.Properties.ResponseTopic != "" {
|
||||
t.Fatal("native response/correlation lost or aliased")
|
||||
}
|
||||
p.Properties.ResponseTopic = "outside/test"
|
||||
if b.receipt(p).Topic != "robot-market/test/v1/device/acks" {
|
||||
t.Fatal("unusable response topic should use default receipt topic")
|
||||
}
|
||||
}
|
||||
|
||||
func TestReceiptLoopAndRetainedReplayPrevention(t *testing.T) {
|
||||
b := testBackend()
|
||||
b.Client.Config.Topics.ObserveAll = true
|
||||
for _, topic := range []string{"acks", "commands", "receipts"} {
|
||||
if b.receipt(&paho.Publish{Topic: "robot-market/test/v1/device/" + topic, Payload: []byte("hello")}) != nil {
|
||||
t.Fatal("ACK loop possible")
|
||||
}
|
||||
}
|
||||
for _, p := range []*paho.Publish{
|
||||
{Topic: "robot-market/test/v1/device/status", Retain: true, Payload: []byte("online")},
|
||||
{Topic: "robot-market/test/v1/custom/topic", Payload: []byte(`{"type":"receipt_ack","ok":true}`)},
|
||||
{Topic: "outside/device/topic", Payload: []byte("hello")},
|
||||
} {
|
||||
if b.receipt(p) != nil {
|
||||
t.Fatal("reply created for replay, receipt or external topic")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestReceiptDuplicateDeliveryRepliesWithoutCommandExecution(t *testing.T) {
|
||||
b := testBackend()
|
||||
b.Client.Config.Topics.ObserveAll = true
|
||||
b.Client.Config.Receipts.Enabled = true
|
||||
p := &paho.Publish{Topic: "robot-market/test/v1/device/telemetry", Payload: []byte("same message")}
|
||||
b.Handle(p)
|
||||
b.Handle(p)
|
||||
if len(b.receipts) != 2 || len(b.pending) != 0 {
|
||||
t.Fatal("duplicate receipt handling must not execute commands")
|
||||
}
|
||||
}
|
||||
|
||||
func TestReceiptQueueFailureAndShutdown(t *testing.T) {
|
||||
b := testBackend()
|
||||
b.Client.Config.Topics.ObserveAll = true
|
||||
b.Client.Config.Receipts.Enabled = true
|
||||
b.receipts = make(chan *paho.Publish, 1)
|
||||
p := &paho.Publish{Topic: "robot-market/test/v1/device/telemetry", Payload: []byte("hello")}
|
||||
b.Handle(p)
|
||||
b.Handle(p)
|
||||
_, counts := b.Client.Events.Snapshot()
|
||||
if counts[events.Kind("receipt_ack_dropped")] != 1 {
|
||||
t.Fatal("queue overflow invisible")
|
||||
}
|
||||
b.PublishReceipt = func(context.Context, *paho.Publish) error { return errors.New("test disconnect") }
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
done := make(chan struct{})
|
||||
go func() { b.receiptWorker(ctx); close(done) }()
|
||||
deadline := time.Now().Add(time.Second)
|
||||
for {
|
||||
_, counts = b.Client.Events.Snapshot()
|
||||
if counts[events.Kind("receipt_ack_failed")] == 1 {
|
||||
break
|
||||
}
|
||||
if time.Now().After(deadline) {
|
||||
t.Fatal("failure not recorded")
|
||||
}
|
||||
time.Sleep(time.Millisecond)
|
||||
}
|
||||
cancel()
|
||||
select {
|
||||
case <-done:
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("worker shutdown blocked")
|
||||
}
|
||||
history, _ := b.Client.Events.Snapshot()
|
||||
if !strings.Contains(history[len(history)-1].Outcome, "not confirmed") {
|
||||
t.Fatal("failure claims success")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user