Files
go-sip/internal/calllog/log_test.go
T

131 lines
4.0 KiB
Go

package calllog
import (
"encoding/json"
"os"
"path/filepath"
"strings"
"sync"
"testing"
"time"
)
func TestAppendCorrelatesPhoneWithoutWritingOriginal(t *testing.T) {
directory := t.TempDir()
logger, err := New(filepath.Join(directory, "logs", "calls.jsonl"), []byte("0123456789abcdef"), func() time.Time {
return time.Date(2026, 9, 19, 1, 2, 3, 4, time.UTC)
})
if err != nil {
t.Fatal(err)
}
phone := "15003164745"
if err := logger.Append(Event{
EventID: "event-1", EventType: "sip.status", Phone: phone,
ExecutionID: "exec-1", TaskID: "task-1", AttemptID: "attempt-1", CallID: "call-1",
RoutePolicyID: "route-sip-first", CallerProfileID: "caller-sip-first", TrunkID: "provider-primary",
SIPStage: "invite", SIPStatusCode: 183, Status: "ringing", ReasonCode: "provisional",
RecordingID: "recording-1", RecordingState: "pending", DurationMS: 120,
}); err != nil {
t.Fatal(err)
}
if err := logger.Append(Event{
EventID: "event-2", EventType: "call.finished", Phone: phone,
ExecutionID: "exec-1", CallID: "call-1", Result: "no_answer", ReasonCode: "provider_480",
RecordingState: "failed", DurationMS: 3000,
}); err != nil {
t.Fatal(err)
}
data, err := os.ReadFile(logger.Path())
if err != nil {
t.Fatal(err)
}
text := string(data)
if strings.Contains(text, phone) {
t.Fatalf("call log contains original phone: %s", text)
}
lines := strings.Split(strings.TrimSpace(text), "\n")
if len(lines) != 2 {
t.Fatalf("got %d lines, want 2", len(lines))
}
var first, second map[string]any
if err := json.Unmarshal([]byte(lines[0]), &first); err != nil {
t.Fatal(err)
}
if err := json.Unmarshal([]byte(lines[1]), &second); err != nil {
t.Fatal(err)
}
if first["phone_ref"] != second["phone_ref"] {
t.Fatalf("same phone must have one reference: %#v %#v", first["phone_ref"], second["phone_ref"])
}
if first["phone_mask"] != "*******4745" {
t.Fatalf("unexpected phone mask: %v", first["phone_mask"])
}
if first["trunk_id"] != "provider-primary" || first["sip_stage"] != "invite" || first["sip_status_code"] != float64(183) {
t.Fatalf("missing SIP correlation fields: %#v", first)
}
if first["recording_id"] != "recording-1" || second["result"] != "no_answer" {
t.Fatalf("missing recording/result fields: %#v %#v", first, second)
}
if mode := fileMode(t, logger.Path()); mode.Perm() != 0o600 {
t.Fatalf("log mode is %o, want 600", mode.Perm())
}
}
func TestAppendRejectsUnredactedOrInvalidIdentity(t *testing.T) {
logger, err := New(filepath.Join(t.TempDir(), "calls.jsonl"), []byte("0123456789abcdef"), nil)
if err != nil {
t.Fatal(err)
}
for _, event := range []Event{
{EventType: "call.status"},
{EventType: "call.status", PhoneRef: "15003164745", PhoneMask: "15003164745"},
{EventType: "call.status", PhoneRef: "hmac-sha256:bad", PhoneMask: "*******4745"},
{EventType: "call.status", Phone: "15003164745", SIPStatusCode: 700},
} {
if err := logger.Append(event); err == nil {
t.Fatalf("event %#v was accepted", event)
}
}
}
func TestAppendSerializesConcurrentEvents(t *testing.T) {
logger, err := New(filepath.Join(t.TempDir(), "calls.jsonl"), []byte("0123456789abcdef"), nil)
if err != nil {
t.Fatal(err)
}
var wait sync.WaitGroup
for index := 0; index < 32; index++ {
wait.Add(1)
go func(index int) {
defer wait.Done()
if err := logger.Append(Event{EventType: "call.status", Phone: "15830461047", EventID: "event-" + string(rune('a'+index))}); err != nil {
t.Errorf("append %d: %v", index, err)
}
}(index)
}
wait.Wait()
data, err := os.ReadFile(logger.Path())
if err != nil {
t.Fatal(err)
}
if lines := strings.Count(strings.TrimSpace(string(data)), "\n") + 1; lines != 32 {
t.Fatalf("got %d serialized events, want 32", lines)
}
}
func TestNewRequiresPhoneKey(t *testing.T) {
if _, err := New(filepath.Join(t.TempDir(), "calls.jsonl"), []byte("short"), nil); err == nil {
t.Fatal("short key was accepted")
}
}
func fileMode(t *testing.T, path string) os.FileMode {
t.Helper()
info, err := os.Stat(path)
if err != nil {
t.Fatal(err)
}
return info.Mode()
}