Files
go-sip/internal/dispatcher/consumer_test.go
T

191 lines
5.2 KiB
Go

package dispatcher
import (
"bytes"
"encoding/json"
"errors"
"testing"
"time"
"git.ipao.vip/rogee/go-sip/contracts"
"git.ipao.vip/rogee/go-sip/internal/contract"
"git.ipao.vip/rogee/go-sip/internal/mq"
"git.ipao.vip/rogee/go-sip/internal/store"
"git.ipao.vip/rogee/go-sip/internal/tenant"
)
func TestMQCommandQueryPersistsBeforeAcceptance(t *testing.T) {
s, err := store.Open(":memory:")
if err != nil {
t.Fatal(err)
}
defer s.Close()
const id = "c046b893-8628-4589-ae50-619d049248a6"
if err := s.BindDispatcherID(id); err != nil {
t.Fatal(err)
}
d, err := New(s, nil, nil)
if err != nil {
t.Fatal(err)
}
raw, err := contracts.Files.ReadFile("upstream/v1/examples/command-query.json")
if err != nil {
t.Fatal(err)
}
var message map[string]any
if err := json.Unmarshal(raw, &message); err != nil {
t.Fatal(err)
}
message["issued_at"] = time.Now().UTC().Format(time.RFC3339Nano)
message["not_after"] = time.Now().Add(time.Minute).UTC().Format(time.RFC3339Nano)
raw, err = json.Marshal(message)
if err != nil {
t.Fatal(err)
}
route, err := tenant.NewDispatcherRoute(id, "tenant-a")
if err != nil {
t.Fatal(err)
}
if err := d.AcceptMQMessage(raw, route.InboundKey); err != nil {
t.Fatal(err)
}
if err := d.AcceptMQMessage(raw, route.InboundKey); err != nil {
t.Fatal(err)
}
var count int
if err := s.DB().QueryRow(`SELECT COUNT(*) FROM outbox`).Scan(&count); err != nil {
t.Fatal(err)
}
if count != 1 {
t.Fatalf("expected one persistent response before ACK, got %d", count)
}
}
func TestMQReplayPersistsDecisionBeforeAcknowledgment(t *testing.T) {
for _, name := range []string{"call-replay", "command-replay"} {
t.Run(name, func(t *testing.T) {
s, err := store.Open(":memory:")
if err != nil {
t.Fatal(err)
}
defer s.Close()
const id = "c046b893-8628-4589-ae50-619d049248a6"
if err := s.BindDispatcherID(id); err != nil {
t.Fatal(err)
}
d, err := New(s, nil, nil)
if err != nil {
t.Fatal(err)
}
raw, err := contracts.Files.ReadFile("upstream/v1/examples/" + name + ".json")
if err != nil {
t.Fatal(err)
}
var request map[string]any
if err := json.Unmarshal(raw, &request); err != nil {
t.Fatal(err)
}
request["issued_at"] = time.Now().UTC().Format(time.RFC3339Nano)
request["not_after"] = time.Now().Add(time.Minute).UTC().Format(time.RFC3339Nano)
raw, err = json.Marshal(request)
if err != nil {
t.Fatal(err)
}
route, err := tenant.NewDispatcherRoute(id, "tenant-a")
if err != nil {
t.Fatal(err)
}
if err := d.AcceptMQMessage(raw, route.InboundKey); err != nil {
t.Fatal(err)
}
if err := d.AcceptMQMessage(raw, route.InboundKey); err != nil {
t.Fatal(err)
}
var count int
if err := s.DB().QueryRow(`SELECT COUNT(*) FROM mq_command_receipts`).Scan(&count); err != nil {
t.Fatal(err)
}
if count != 1 {
t.Fatal("replay decision not durably deduplicated")
}
})
}
}
func TestMQCallQueryPersistsResponse(t *testing.T) {
s, err := store.Open(":memory:")
if err != nil {
t.Fatal(err)
}
defer s.Close()
const id = "c046b893-8628-4589-ae50-619d049248a6"
if err := s.BindDispatcherID(id); err != nil {
t.Fatal(err)
}
d, err := New(s, nil, nil)
if err != nil {
t.Fatal(err)
}
raw, err := contracts.Files.ReadFile("upstream/v1/examples/call-query.json")
if err != nil {
t.Fatal(err)
}
var request map[string]any
if err := json.Unmarshal(raw, &request); err != nil {
t.Fatal(err)
}
request["issued_at"] = time.Now().UTC().Format(time.RFC3339Nano)
request["not_after"] = time.Now().Add(time.Minute).UTC().Format(time.RFC3339Nano)
raw, err = json.Marshal(request)
if err != nil {
t.Fatal(err)
}
route, err := tenant.NewDispatcherRoute(id, "tenant-a")
if err != nil {
t.Fatal(err)
}
if err := d.AcceptMQMessage(raw, route.InboundKey); err != nil {
t.Fatal(err)
}
var count int
if err := s.DB().QueryRow(`SELECT COUNT(*) FROM outbox WHERE json_extract(body,'$.message_type')='call.query.result'`).Scan(&count); err != nil {
t.Fatal(err)
}
if count != 1 {
t.Fatal("call query was acknowledged without a persisted response")
}
}
func TestInvalidServiceEncodingIsPermanent(t *testing.T) {
raw, err := contracts.Files.ReadFile("upstream/v1/examples/command-query.json")
if err != nil {
t.Fatal(err)
}
raw = bytes.Replace(raw, []byte("command-a"), []byte{0xff}, 1)
if !bytes.Contains(raw, []byte{0xff}) {
t.Fatal("invalid UTF-8 fixture was not constructed")
}
_, err = contract.DecodeService(raw)
if err == nil || !isPermanentCommandError(err) {
t.Fatalf("invalid encoding would be requeued: %v", err)
}
}
func TestPermanentCommandClassification(t *testing.T) {
if !isPermanentCommandError(contract.ErrInvalidTenantKey) {
t.Fatal("tenant validation must be permanent")
}
for _, raw := range [][]byte{[]byte(`{}`), []byte(`{"schema_version":"invalid"}`), []byte(`{`)} {
_, _, err := contract.DecodeExecute(raw)
if err == nil || !isPermanentCommandError(err) {
t.Fatalf("actual malformed message must be permanent: %v", err)
}
}
if isPermanentCommandError(errors.New("sqlite busy")) {
t.Fatal("storage failure must be retried")
}
if !mq.IsPermanent(mq.Permanent(contract.ErrInvalidTenantKey)) {
t.Fatal("permanent wrapper was not recognized")
}
}