161 lines
4.7 KiB
Go
161 lines
4.7 KiB
Go
package sipcall
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"git.ipao.vip/rogee/go-sip/internal/asterisk"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
type fakeCall struct {
|
|
closeErr error
|
|
ended bool
|
|
ctx context.Context
|
|
}
|
|
|
|
func (c *fakeCall) Close() error { return c.closeErr }
|
|
func (c *fakeCall) EndConfirmed() bool { return c.ended }
|
|
func (c *fakeCall) Done() context.Context { return c.ctx }
|
|
|
|
type fakeHost struct {
|
|
busy, applyErr, dialErr, restoreErr error
|
|
calls, restores int
|
|
call *fakeCall
|
|
}
|
|
|
|
func (h *fakeHost) CheckIdle(context.Context) error { return h.busy }
|
|
func (h *fakeHost) Apply(context.Context, Config) (func() error, error) {
|
|
return func() error { h.restores++; return h.restoreErr }, h.applyErr
|
|
}
|
|
func (h *fakeHost) Dial(ctx context.Context, request asterisk.NativeDial) (testCall, error) {
|
|
h.calls++
|
|
if h.dialErr != nil {
|
|
return nil, h.dialErr
|
|
}
|
|
if err := request.BindSIPCall(request.ExecutionID, "sip-id-123"); err != nil {
|
|
return nil, err
|
|
}
|
|
if h.call == nil {
|
|
h.call = &fakeCall{ended: true, ctx: context.Background()}
|
|
}
|
|
return h.call, nil
|
|
}
|
|
func (h *fakeHost) Close() error { return nil }
|
|
|
|
type fakeCapture struct {
|
|
stops int
|
|
err error
|
|
}
|
|
|
|
func (c *fakeCapture) Check() error { return nil }
|
|
func (c *fakeCapture) Stop(_ context.Context, _ string) (Evidence, error) {
|
|
c.stops++
|
|
return Evidence{PCAP: "capture.pcap", SIPTimeline: "sip.tsv", Packets: 10, FinalINVITECode: 200}, c.err
|
|
}
|
|
|
|
func fixtureConfig(t *testing.T) Config {
|
|
t.Helper()
|
|
c, err := ParseConfig(strings.NewReader(validENV))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
c.OutputDir = t.TempDir()
|
|
if err := os.Chmod(c.OutputDir, 0700); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
c.resultDir = c.OutputDir
|
|
c.Hold = time.Millisecond
|
|
return c
|
|
}
|
|
|
|
func TestRunDefaultNeedsNoCaptureToolsAndDialsOnce(t *testing.T) {
|
|
c := fixtureConfig(t)
|
|
h := &fakeHost{}
|
|
r, err := run(context.Background(), c, "18601010101", false, h, func(context.Context, Config, string) (captureSession, error) {
|
|
t.Fatal("capture must be completely absent by default")
|
|
return nil, nil
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if r.Status != "connected" || !r.EndConfirmed || r.CallID != "sip-id-123" || r.DialedCallee != "708918601010101" || h.calls != 1 || h.restores != 1 || r.Evidence != nil {
|
|
t.Fatalf("wrong single call: %+v", r)
|
|
}
|
|
data, err := os.ReadFile(filepath.Join(r.Directory, "result.json"))
|
|
if err != nil || !strings.Contains(string(data), "connected") {
|
|
t.Fatalf("missing private result: %v", err)
|
|
}
|
|
info, _ := os.Stat(r.Directory)
|
|
if info.Mode().Perm() != 0700 {
|
|
t.Fatal("results must be private")
|
|
}
|
|
}
|
|
|
|
func TestRunFailuresAreVisibleAndNeverRetry(t *testing.T) {
|
|
for _, phase := range []string{"busy", "apply", "capture", "dial", "end", "restore", "decode"} {
|
|
t.Run(phase, func(t *testing.T) {
|
|
c := fixtureConfig(t)
|
|
h := &fakeHost{}
|
|
cap := &fakeCapture{}
|
|
debug := phase == "capture" || phase == "decode"
|
|
sentinel := errors.New("diagnostic failure")
|
|
switch phase {
|
|
case "busy":
|
|
h.busy = sentinel
|
|
case "apply":
|
|
h.applyErr = sentinel
|
|
case "dial":
|
|
h.dialErr = sentinel
|
|
case "end":
|
|
h.call = &fakeCall{ctx: context.Background()}
|
|
case "restore":
|
|
h.restoreErr = sentinel
|
|
case "decode":
|
|
cap.err = sentinel
|
|
}
|
|
r, err := run(context.Background(), c, "18601010101", debug, h, func(context.Context, Config, string) (captureSession, error) {
|
|
if phase == "capture" {
|
|
return nil, sentinel
|
|
}
|
|
return cap, nil
|
|
})
|
|
if err == nil || r.Status == "connected" || h.calls > 1 {
|
|
t.Fatalf("failure disguised: %+v %v", r, err)
|
|
}
|
|
if (phase == "busy" || phase == "capture" || phase == "apply") && h.calls != 0 {
|
|
t.Fatal("preflight failure dialed")
|
|
}
|
|
if phase == "end" && h.restores != 0 {
|
|
t.Fatal("unknown live channel must retain its configuration")
|
|
}
|
|
if debug && phase == "decode" && cap.stops != 1 {
|
|
t.Fatal("capture must always be finalized")
|
|
}
|
|
if _, err = os.Stat(filepath.Join(r.Directory, "result.json")); err != nil {
|
|
t.Fatalf("failure detail missing: %v", err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRunDebugHasLinkedEvidenceAndCancellationDoesNotRedial(t *testing.T) {
|
|
c := fixtureConfig(t)
|
|
h := &fakeHost{}
|
|
cap := &fakeCapture{}
|
|
r, err := run(context.Background(), c, "18601010101", true, h, func(context.Context, Config, string) (captureSession, error) { return cap, nil })
|
|
if err != nil || r.Evidence == nil || cap.stops != 1 {
|
|
t.Fatalf("evidence missing: %+v %v", r, err)
|
|
}
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
cancel()
|
|
h = &fakeHost{}
|
|
_, err = run(ctx, c, "18601010101", false, h, nil)
|
|
if err == nil || h.calls != 0 {
|
|
t.Fatal("cancelled invocation must not dial")
|
|
}
|
|
}
|