package store import ( "errors" "os" "path/filepath" "strings" "testing" "time" "git.ipao.vip/rogee/go-sip/internal/configread" ) func preparedCurrentCallStore(t *testing.T) *Store { t.Helper() s, err := Open(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.DiscoveredTask{{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) ExecuteCommand { return ExecuteCommand{DispatcherID: currentDispatcherID, EventID: id, TenantID: 1001, TaskID: "task-asr", Callee: "15003164745", IssuedAt: "2026-09-21T01:30:00Z"} } func currentReservation() CallReservation { body, err := os.ReadFile("../../contracts/schema/examples/config-read-task-asr.json") if err != nil { panic(err) } var task configread.Task if err := task.UnmarshalJSON(body); err != nil { panic(err) } hash, err := configread.TaskConfigurationSHA256(task) if err != nil { panic(err) } return CallReservation{TrunkID: "trunk-mock", SIPRevision: 8, TaskRevision: 1, TaskConfigSHA256: hash, CallerID: "BD00000000", DialedCallee: "15003164745", Deadline: time.Date(2026, 9, 21, 1, 32, 0, 0, time.UTC)} } func TestExecuteInboxPreventsDuplicateOriginationAfterRestart(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 TestExecuteRejectsOnlyOnceWithoutFinalResult(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 TestAgentExplicitPreDialRejectionReleasesReservedCapacity(t *testing.T) { s := preparedCurrentCallStore(t) at := time.Date(2026, 9, 21, 1, 30, 0, 0, time.UTC) cmd := currentCall("call-agent-rejected") 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) } if err := s.RejectUnissuedExecute(cmd.DispatcherID, cmd.EventID, "Agent refused before outbound call"); err != nil { t.Fatal(err) } outbox, err := s.ListPendingOutbox(cmd.DispatcherID) if err != nil || len(outbox) != 1 || !strings.Contains(string(outbox[0].Body), `"status":"rejected"`) { t.Fatalf("pre-dial failure must produce one durable honest receipt: %+v %v", outbox, err) } if err := s.RejectUnissuedExecute(cmd.DispatcherID, cmd.EventID, "Agent refused before outbound call"); err == nil { t.Fatal("duplicate rejection cannot create a second receipt") } if _, created, err := s.RecordExecute(cmd); err != nil || created { t.Fatalf("rejected call was reaccepted: %v %v", created, err) } occupied, err := s.TrunkOccupancy(cmd.DispatcherID) if err != nil || occupied["trunk-mock"] != 0 { t.Fatalf("definite pre-dial failure still occupies capacity: %+v %v", occupied, err) } } func TestExecuteQuotaCountsUnknownAndReleasesOnlyConfirmedEnd(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, ErrCapacity) { 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, ErrCapacity) { 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 TestConfirmedEndBeforeOriginateAckIsDurableAndIdempotent(t *testing.T) { s := preparedCurrentCallStore(t) cmd := currentCall("call-ended-before-ack") 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.FinishExecute(cmd.DispatcherID, cmd.EventID); err != nil { t.Fatalf("confirmed Agent end raced with originate acknowledgment: %v", err) } if err := s.MarkExecuteDispatched(cmd.DispatcherID, cmd.EventID); err != nil { t.Fatalf("late originate acknowledgment was not idempotent: %v", err) } if err := s.MarkExecuteUnknown(cmd.DispatcherID, cmd.EventID); err != nil { t.Fatalf("late RPC timeout obscured a confirmed call end: %v", err) } if err := s.FinishExecute(cmd.DispatcherID, cmd.EventID); err != nil { t.Fatalf("Agent end-report retry was not idempotent: %v", err) } outbox, err := s.ListPendingOutbox(cmd.DispatcherID) if err != nil || len(outbox) != 1 || outbox[0].EventID != cmd.EventID || !strings.Contains(string(outbox[0].Body), `"status":"dispatched"`) { t.Fatalf("early end lost or duplicated original acknowledgment: outbox=%+v err=%v", outbox, err) } occupied, err := s.TrunkOccupancy(cmd.DispatcherID) if err != nil || occupied["trunk-mock"] != 0 { t.Fatalf("confirmed end held occupancy after acknowledgment: occupied=%+v err=%v", occupied, err) } if _, created, err := s.RecordExecute(cmd); err != nil || created { t.Fatalf("restarted finished call could originate again: created=%t err=%v", created, err) } } func TestConfirmedUnknownCallEndReleasesOnlyAfterDurableAck(t *testing.T) { s := preparedCurrentCallStore(t) cmd := currentCall("call-ended-after-unknown") 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.MarkExecuteUnknown(cmd.DispatcherID, cmd.EventID); err != nil { t.Fatal(err) } if err := s.FinishExecute(cmd.DispatcherID, cmd.EventID); err != nil { t.Fatalf("confirmed unknown call end did not release: %v", err) } outbox, err := s.ListPendingOutbox(cmd.DispatcherID) if err != nil || len(outbox) != 1 || outbox[0].EventID != cmd.EventID { t.Fatalf("confirmed unknown execution lacked one durable acknowledgment: outbox=%+v err=%v", outbox, err) } occupied, err := s.TrunkOccupancy(cmd.DispatcherID) if err != nil || occupied["trunk-mock"] != 0 { t.Fatalf("confirmed unknown call still occupied trunk: occupied=%+v err=%v", occupied, err) } } func TestEarlyEndOutboxFailureRetainsUnknownOccupancy(t *testing.T) { s := preparedCurrentCallStore(t) cmd := currentCall("call-early-end-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_early_ack BEFORE INSERT ON dispatcher_outbox BEGIN SELECT RAISE(ABORT,'injected acknowledgment fault'); END`); err != nil { t.Fatal(err) } if err := s.FinishExecute(cmd.DispatcherID, cmd.EventID); err == nil { t.Fatal("acknowledgment write failure released the call") } occupied, err := s.TrunkOccupancy(cmd.DispatcherID) if err != nil || occupied["trunk-mock"] != 1 { t.Fatalf("failed acknowledgment lost the unknown occupancy: occupied=%+v err=%v", occupied, err) } if _, err := s.db.Exec(`DROP TRIGGER fail_early_ack`); err != nil { t.Fatal(err) } if err := s.FinishExecute(cmd.DispatcherID, cmd.EventID); err != nil { t.Fatalf("original confirmed end could not resume: %v", err) } } func TestEndAndOriginateAckRaceNeverDuplicate(t *testing.T) { for attempt := 0; attempt < 30; attempt++ { s := preparedCurrentCallStore(t) cmd := currentCall("call-concurrent-end") 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) } errorsCh := make(chan error, 2) go func() { errorsCh <- s.FinishExecute(cmd.DispatcherID, cmd.EventID) }() go func() { errorsCh <- s.MarkExecuteDispatched(cmd.DispatcherID, cmd.EventID) }() for range 2 { if err := <-errorsCh; err != nil { t.Fatal(err) } } outbox, err := s.ListPendingOutbox(cmd.DispatcherID) if err != nil || len(outbox) != 1 { t.Fatalf("race produced multiple acknowledgments: outbox=%+v err=%v", outbox, err) } occupied, err := s.TrunkOccupancy(cmd.DispatcherID) if err != nil || occupied["trunk-mock"] != 0 { t.Fatalf("race failed to release confirmed end: occupied=%+v err=%v", occupied, err) } } } func TestExecuteOutboxFailureDoesNotMarkOriginatedCallDelivered(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) } }