Files
go-sip/internal/store/current_calls_test.go
T

163 lines
6.6 KiB
Go

package store
import (
"errors"
"path/filepath"
"strings"
"testing"
"time"
"git.ipao.vip/rogee/go-sip/internal/configread"
)
func preparedCurrentCallStore(t *testing.T) *CurrentStore {
t.Helper()
s, err := OpenCurrent(filepath.Join(t.TempDir(), "state.db"))
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { _ = s.Close() })
task := currentStoreSnapshot(t)
task.SIP.Trunks = []byte(strings.Replace(string(task.SIP.Trunks), `"max_concurrent_calls":null`, `"max_concurrent_calls":2`, 1))
if err := s.ApplyDiscoverySnapshot(currentDispatcherID, []configread.CurrentDiscoveredTask{{TaskID: task.Task.TaskID, TenantID: task.Task.TenantID, TaskRevision: task.Task.TaskRevision, Status: "running"}}); err != nil {
t.Fatal(err)
}
if err := s.SaveSnapshot(task); err != nil {
t.Fatal(err)
}
if err := s.MarkReadyForSIP(currentDispatcherID, 8); err != nil {
t.Fatal(err)
}
return s
}
func currentCall(id string) CurrentExecuteCommand {
return CurrentExecuteCommand{DispatcherID: currentDispatcherID, EventID: id, TenantID: 1001, TaskID: "task-asr", Callee: "15003164745", IssuedAt: "2026-09-21T01:30:00Z"}
}
func currentReservation() CurrentCallReservation {
return CurrentCallReservation{TrunkID: "trunk-mock", SIPRevision: 8, CallerID: "BD00000000", DialedCallee: "15003164745", Deadline: time.Date(2026, 9, 21, 1, 32, 0, 0, time.UTC)}
}
func TestCurrentExecuteInboxPreventsDuplicateOriginationAfterRestart(t *testing.T) {
s := preparedCurrentCallStore(t)
cmd := currentCall("call-1")
first, created, err := s.RecordExecute(cmd)
if err != nil || !created || first.Status != "pending" {
t.Fatalf("first durable receipt: %+v created=%v err=%v", first, created, err)
}
second, created, err := s.RecordExecute(cmd)
if err != nil || created || second.Status != "pending" {
t.Fatalf("redelivery not deduplicated: %+v created=%v err=%v", second, created, err)
}
if err := s.ReserveExecute(cmd.DispatcherID, cmd.EventID, currentReservation(), time.Date(2026, 9, 21, 1, 30, 0, 0, time.UTC)); err != nil {
t.Fatal(err)
}
if _, created, err := s.RecordExecute(cmd); err != nil || created {
t.Fatalf("reserved call was reaccepted: created=%v err=%v", created, err)
}
if err := s.ReserveExecute(cmd.DispatcherID, cmd.EventID, currentReservation(), time.Date(2026, 9, 21, 1, 30, 0, 0, time.UTC)); err == nil {
t.Fatal("duplicate receipt reserved a second originate")
}
pending, err := s.ListPendingExecute(currentDispatcherID)
if err != nil || len(pending) != 0 {
t.Fatalf("unknown/reserved call retried: %+v %v", pending, err)
}
if err := s.MarkExecuteUnknown(cmd.DispatcherID, cmd.EventID); err != nil {
t.Fatal(err)
}
if _, created, err := s.RecordExecute(cmd); err != nil || created {
t.Fatalf("unknown call reaccepted: created=%v err=%v", created, err)
}
}
func TestCurrentExecuteRejectsOnlyOnceWithoutFinalResult(t *testing.T) {
s := preparedCurrentCallStore(t)
cmd := currentCall("call-invalid")
if _, _, err := s.RecordExecute(cmd); err != nil {
t.Fatal(err)
}
if err := s.RejectExecute(cmd.DispatcherID, cmd.EventID, "callee not permitted"); err != nil {
t.Fatal(err)
}
outbox, err := s.ListPendingOutbox(currentDispatcherID)
if err != nil || len(outbox) != 1 || outbox[0].EventType != "call.execute" || !strings.Contains(string(outbox[0].Body), `"status":"rejected"`) {
t.Fatalf("rejection outbox: %+v %v", outbox, err)
}
if _, created, err := s.RecordExecute(cmd); err != nil || created {
t.Fatalf("rejected command was accepted twice: %v %v", created, err)
}
if err := s.RejectExecute(cmd.DispatcherID, cmd.EventID, "callee not permitted"); err == nil {
t.Fatal("duplicate rejection created another outbox event")
}
if err := s.MarkOutboxConfirmed(currentDispatcherID, outbox[0].EventID); err != nil {
t.Fatal(err)
}
if pending, err := s.ListPendingOutbox(currentDispatcherID); err != nil || len(pending) != 0 {
t.Fatalf("confirmed outbox was republished: %+v %v", pending, err)
}
var total int
if err := s.db.QueryRow(`SELECT COUNT(*) FROM dispatcher_outbox WHERE dispatcher_id=?`, currentDispatcherID).Scan(&total); err != nil || total != 1 {
t.Fatalf("confirmed outbox vanished before SaaS receipt: %d %v", total, err)
}
}
func TestCurrentExecuteQuotaCountsUnknownAndReleasesOnlyConfirmedEnd(t *testing.T) {
s := preparedCurrentCallStore(t)
at := time.Date(2026, 9, 21, 1, 30, 0, 0, time.UTC)
for _, id := range []string{"call-1", "call-2"} {
cmd := currentCall(id)
if _, _, err := s.RecordExecute(cmd); err != nil {
t.Fatal(err)
}
if err := s.ReserveExecute(cmd.DispatcherID, cmd.EventID, currentReservation(), at); err != nil {
t.Fatal(err)
}
}
third := currentCall("call-3")
if _, _, err := s.RecordExecute(third); err != nil {
t.Fatal(err)
}
if err := s.ReserveExecute(third.DispatcherID, third.EventID, currentReservation(), at); !errors.Is(err, ErrCurrentCapacity) {
t.Fatalf("third call exceeded task/trunk capacity: %v", err)
}
if err := s.MarkExecuteUnknown(currentDispatcherID, "call-1"); err != nil {
t.Fatal(err)
}
if err := s.ReserveExecute(third.DispatcherID, third.EventID, currentReservation(), at); !errors.Is(err, ErrCurrentCapacity) {
t.Fatalf("unknown call released occupancy: %v", err)
}
if err := s.MarkExecuteDispatched(currentDispatcherID, "call-2"); err != nil {
t.Fatal(err)
}
if err := s.FinishExecute(currentDispatcherID, "call-2"); err != nil {
t.Fatal(err)
}
if err := s.ReserveExecute(third.DispatcherID, third.EventID, currentReservation(), at); err != nil {
t.Fatalf("finished call failed to release capacity: %v", err)
}
}
func TestCurrentExecuteOutboxFailureDoesNotMarkOriginatedCallDelivered(t *testing.T) {
s := preparedCurrentCallStore(t)
cmd := currentCall("call-fault")
if _, _, err := s.RecordExecute(cmd); err != nil {
t.Fatal(err)
}
if err := s.ReserveExecute(cmd.DispatcherID, cmd.EventID, currentReservation(), time.Date(2026, 9, 21, 1, 30, 0, 0, time.UTC)); err != nil {
t.Fatal(err)
}
if _, err := s.db.Exec(`CREATE TRIGGER fail_outbox BEFORE INSERT ON dispatcher_outbox BEGIN SELECT RAISE(ABORT,'injected outbox failure'); END`); err != nil {
t.Fatal(err)
}
if err := s.MarkExecuteDispatched(cmd.DispatcherID, cmd.EventID); err == nil {
t.Fatal("marked dispatch despite outbox failure")
}
var status string
if err := s.db.QueryRow(`SELECT status FROM dispatcher_inbox WHERE dispatcher_id=? AND event_id=?`, cmd.DispatcherID, cmd.EventID).Scan(&status); err != nil || status != "dispatching" {
t.Fatalf("lost pre-originate fence after outbox failure: %q %v", status, err)
}
if _, created, err := s.RecordExecute(cmd); err != nil || created {
t.Fatalf("originated command redialed after outbox failure: %v %v", created, err)
}
}