Files
mqtt-5-test/internal/integration/reliability_test.go
T
Ryan 71ef622b4f
Test and deploy MQTT backend / test (push) Successful in 2m8s
Test and deploy MQTT backend / deploy (push) Successful in 18s
Deploy MQTT test backend with private viewer and Gitea CI/CD
2026-10-08 14:48:53 +03:30

118 lines
4.0 KiB
Go

package integration
import (
"context"
"strings"
"testing"
"time"
"github.com/eclipse/paho.golang/paho"
"robot-market/test-mqtt-5/internal/backend"
"robot-market/test-mqtt-5/internal/contracts"
"robot-market/test-mqtt-5/internal/events"
"robot-market/test-mqtt-5/internal/mqtt"
)
func TestRemoteBrokerQueuedMessageExpiry(t *testing.T) {
deviceCfg := remoteConfig(t, "sim-001", false)
deviceCfg.MQTT.ClientID = "rm5-expiry-" + backend.ID()[:12]
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
device := mqtt.New(deviceCfg, store(deviceCfg))
if err := device.Start(ctx, []string{"commands"}, nil); err != nil {
t.Fatal(err)
}
if err := device.Close(); err != nil {
t.Fatal(err)
}
serverCfg := remoteConfig(t, "test-backend", false)
server := mqtt.New(serverCfg, store(serverCfg))
if err := server.Start(ctx, []string{"acks"}, nil); err != nil {
t.Fatal(err)
}
defer server.Close()
// Application deadline remains valid; the broker must expire this queued packet.
p := mqtt.Packet(mqtt.Topic(serverCfg, "commands"), 1, false, backend.NewCommand("sim-001", "ping", backend.ID(), 15*time.Second))
p.Properties.MessageExpiry = paho.Uint32(1)
if err := server.Publish(ctx, p); err != nil {
t.Fatal(err)
}
time.Sleep(2 * time.Second)
deviceCfg.MQTT.CleanStart = false
device = mqtt.New(deviceCfg, store(deviceCfg))
if err := device.Start(ctx, []string{"commands"}, nil); err != nil {
t.Fatal(err)
}
defer device.Close()
history, _ := device.Events.Snapshot()
resumed := false
for _, e := range history {
if e.Kind == events.ConnectionUp && strings.Contains(e.Outcome, "session_present=true") {
resumed = true
}
}
if !resumed {
t.Fatal("offline subscriber session was lost; expiry test inconclusive")
}
select {
case <-device.Incoming:
t.Fatal("broker delivered expired queued command")
case <-time.After(300 * time.Millisecond):
}
fresh := mqtt.Packet(mqtt.Topic(serverCfg, "commands"), 1, false, backend.NewCommand("sim-001", "ping", backend.ID(), 15*time.Second))
if err := server.Publish(ctx, fresh); err != nil {
t.Fatal(err)
}
select {
case got := <-device.Incoming:
if string(got.Payload) != string(fresh.Payload) {
t.Fatal("wrong queued packet")
}
case <-time.After(3 * time.Second):
t.Fatal("fresh control command missing")
}
t.Log("broker expiry distinct from valid application deadline, persistent offline queue, fresh control delivery: PASS")
}
func TestRemoteWillAfterAbruptDisconnect(t *testing.T) {
observerCfg := remoteConfig(t, "test-backend", false)
observer := mqtt.New(observerCfg, store(observerCfg))
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
if err := observer.Start(ctx, []string{"status"}, nil); err != nil {
t.Fatal(err)
}
defer observer.Close()
cfg := remoteConfig(t, "sim-001", false)
proxy := newRelay(t, strings.TrimPrefix(cfg.MQTT.URL, "tls://"))
defer proxy.close()
cfg.MQTT.URL = "tls://" + proxy.listener.Addr().String()
cfg.MQTT.ReconnectMin = 100 * time.Millisecond
cfg.MQTT.ReconnectMax = 200 * time.Millisecond
boot := "will-" + backend.ID()[:12]
status := contracts.Status{Envelope: contracts.Envelope{SchemaVersion: 1, DeviceID: "sim-001"}, BootID: boot, State: "offline", WillConfiguredAt: backend.Timestamp(time.Now())}
packet := mqtt.Packet(mqtt.Topic(cfg, "status"), 1, false, status)
client := mqtt.New(cfg, store(cfg))
if err := client.Start(ctx, []string{"commands"}, &paho.WillMessage{Topic: packet.Topic, Payload: packet.Payload, QoS: 1, Retain: false}); err != nil {
t.Fatal(err)
}
defer client.Close()
proxy.pause()
deadline := time.NewTimer(5 * time.Second)
defer deadline.Stop()
for {
select {
case p := <-observer.Incoming:
if strings.Contains(string(p.Payload), boot) {
if !strings.Contains(string(p.Payload), "will_configured_at") {
t.Fatal("Will invents disconnect timestamp")
}
t.Log("broker emits non-retained Will after actual abrupt TLS connection loss: PASS")
return
}
case <-deadline.C:
t.Fatal("Will not observed after abrupt connection loss")
}
}
}