131 lines
4.0 KiB
Go
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()
|
|
}
|