165 lines
5.8 KiB
Go
165 lines
5.8 KiB
Go
package dispatcher
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"testing"
|
|
|
|
"git.ipao.vip/rogee/go-sip/internal/configread"
|
|
)
|
|
|
|
func TestSelectTrunkHonorsTaskLineLimit(t *testing.T) {
|
|
for _, tc := range []struct {
|
|
name string
|
|
limit, task, total int64
|
|
wantErr error
|
|
}{
|
|
{"available", 1, 0, 0, nil},
|
|
{"other task occupancy", 1, 0, 1, nil},
|
|
{"disabled", 0, 0, 0, ErrRuleWait},
|
|
{"task line full", 1, 1, 1, ErrRuleWait},
|
|
{"unknown line occupied", 1, 2, 0, ErrRuleWait},
|
|
{"global line full", 5, 0, 2, ErrRuleWait},
|
|
{"negative limit", -1, 0, 0, ErrRuleInvalid},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
snapshot := policySnapshot(t)
|
|
snapshot.Task.AllowedTrunks = []configread.AllowedTrunk{{TrunkID: "trunk-mock", Concurrency: tc.limit}}
|
|
choice, err := SelectTrunk(snapshot, "15003164745", monday(9, 30), map[string]int64{"trunk-mock": tc.total}, map[string]int64{"trunk-mock": tc.task}, map[string]int64{"trunk-mock": 8})
|
|
if !errors.Is(err, tc.wantErr) {
|
|
t.Fatalf("selected=%q error=%v, want %v", choice.TrunkID, err, tc.wantErr)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSelectTrunkSkipsDisabledAndSaturatedTaskLinesInConfiguredOrder(t *testing.T) {
|
|
for _, limit := range []int64{0, 1} {
|
|
snapshot := policySnapshot(t)
|
|
var trunks []trunkConfig
|
|
if err := json.Unmarshal(snapshot.SIP.Trunks, &trunks); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
second := trunks[0]
|
|
second.TrunkID = "trunk-second"
|
|
trunks = append(trunks, second)
|
|
var err error
|
|
snapshot.SIP.Trunks, err = json.Marshal(trunks)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
snapshot.Task.AllowedTrunks = []configread.AllowedTrunk{{TrunkID: "trunk-mock", Concurrency: limit}, {TrunkID: "trunk-second", Concurrency: 1}}
|
|
choice, err := SelectTrunk(snapshot, "15003164745", monday(9, 30), map[string]int64{"trunk-mock": 1}, map[string]int64{"trunk-mock": 1}, map[string]int64{"trunk-mock": 8, "trunk-second": 8})
|
|
if err != nil || choice.TrunkID != "trunk-second" {
|
|
t.Fatalf("limit=%d selected=%q err=%v", limit, choice.TrunkID, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestSelectTrunkDoesNotHideMissingLineBehindZeroConcurrency(t *testing.T) {
|
|
snapshot := policySnapshot(t)
|
|
snapshot.Task.AllowedTrunks = []configread.AllowedTrunk{{TrunkID: "missing", Concurrency: 0}}
|
|
if _, err := SelectTrunk(snapshot, "15003164745", monday(9, 30), nil, nil, nil); !errors.Is(err, ErrRuleInvalid) {
|
|
t.Fatalf("disabled line hid a broken reference: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestSelectTrunkRejectsDuplicateTaskLineIDs(t *testing.T) {
|
|
snapshot := policySnapshot(t)
|
|
snapshot.Task.AllowedTrunks = []configread.AllowedTrunk{{TrunkID: "trunk-mock", Concurrency: 1}, {TrunkID: "trunk-mock", Concurrency: 2}}
|
|
_, err := SelectTrunk(snapshot, "15003164745", monday(9, 30), nil, nil, map[string]int64{"trunk-mock": 8})
|
|
if !errors.Is(err, ErrRuleInvalid) {
|
|
t.Fatalf("duplicate task line accepted: %v", err)
|
|
}
|
|
}
|
|
|
|
func editedTaskLine(t *testing.T, snapshot configread.Snapshot, revision, limit int64) configread.Snapshot {
|
|
t.Helper()
|
|
var task map[string]json.RawMessage
|
|
if err := json.Unmarshal(snapshot.Task.Raw, &task); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for key, value := range map[string]any{
|
|
"task_revision": revision,
|
|
"allowed_trunk_ids": []configread.AllowedTrunk{{TrunkID: "trunk-mock", Concurrency: limit}},
|
|
} {
|
|
raw, err := json.Marshal(value)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
task[key] = raw
|
|
}
|
|
raw, err := json.Marshal(task)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := json.Unmarshal(raw, &snapshot.Task); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return snapshot
|
|
}
|
|
|
|
func TestExecuteControllerKeepsUnknownTaskLineSlotUntilConfirmedEnd(t *testing.T) {
|
|
c, origin, _, _ := newExecuteFixture(t)
|
|
snapshot := editedTaskLine(t, policySnapshot(t), 2, 1)
|
|
if state, err := c.Store.CompleteEdit(snapshot, "edit-line-limit"); err != nil || state != "applied" {
|
|
t.Fatalf("edit=%q err=%v", state, err)
|
|
}
|
|
ctx := context.Background()
|
|
lostReply := errors.New("simulated originate reply lost")
|
|
origin.err = lostReply
|
|
if err := c.ProcessExecute(ctx, executeBody(t, "call-first", "15003164745")); !errors.Is(err, lostReply) {
|
|
t.Fatalf("unknown originate outcome=%v", err)
|
|
}
|
|
origin.err = nil
|
|
if err := c.ProcessExecute(ctx, executeBody(t, "call-second", "15003164745")); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(origin.calls) != 1 {
|
|
t.Fatalf("task-line limit exceeded: attempts=%d", len(origin.calls))
|
|
}
|
|
if err := c.ProcessPending(ctx); err != nil || len(origin.calls) != 1 {
|
|
t.Fatalf("unknown call was retried or lost its slot: calls=%d err=%v", len(origin.calls), err)
|
|
}
|
|
if err := c.Store.FinishExecute(c.DispatcherID, "call-first"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := c.ProcessPending(ctx); err != nil || len(origin.calls) != 2 {
|
|
t.Fatalf("confirmed end did not admit the pending call: calls=%d err=%v", len(origin.calls), err)
|
|
}
|
|
if origin.calls[1].Snapshot.Task.TaskRevision != 2 || origin.calls[1].Snapshot.Task.AllowedTrunks[0].Concurrency != 1 {
|
|
t.Fatal("new task-line limit was not frozen into the approved call")
|
|
}
|
|
}
|
|
|
|
type editingTaskOriginator struct {
|
|
*fakeOriginator
|
|
edit func()
|
|
}
|
|
|
|
func (o *editingTaskOriginator) LoadedTrunks(ctx context.Context) (map[string]int64, error) {
|
|
if o.edit != nil {
|
|
edit := o.edit
|
|
o.edit = nil
|
|
edit()
|
|
}
|
|
return o.fakeOriginator.LoadedTrunks(ctx)
|
|
}
|
|
|
|
func TestExecuteControllerRejectsSelectionIfTaskChangesBeforeReservation(t *testing.T) {
|
|
c, origin, _, _ := newExecuteFixture(t)
|
|
c.Originator = &editingTaskOriginator{fakeOriginator: origin, edit: func() {
|
|
snapshot := editedTaskLine(t, policySnapshot(t), 2, 0)
|
|
if state, err := c.Store.CompleteEdit(snapshot, "disable-before-reserve"); err != nil || state != "applied" {
|
|
t.Fatalf("edit=%q err=%v", state, err)
|
|
}
|
|
}}
|
|
if err := c.ProcessExecute(context.Background(), executeBody(t, "call-stale-choice", "15003164745")); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(origin.calls) != 0 {
|
|
t.Fatal("stale selection originated after the task line was disabled")
|
|
}
|
|
}
|