package callruntime import ( "bytes" "crypto/sha256" "encoding/binary" "encoding/hex" "os" "path/filepath" "testing" "git.ipao.vip/rogee/go-sip/internal/callflow" ) func TestConversationRecordingFactsMatchFiles(t *testing.T) { directory := t.TempDir() incoming := [][]byte{{1, 0, 2, 0}, make([]byte, 32000)} outgoing := [][]byte{{4, 0}, {5, 0, 6, 0}} in, out, err := persistConversationRecordings(directory, "channel/with:separators", callflow.Result{InboundTurns: incoming, OutboundTurns: outgoing}) if err != nil { t.Fatal(err) } if len(in) != 2 || len(out) != 2 { t.Fatal("lost recording segments") } for side, facts := range [][]RecordingFact{in, out} { expected := [][][]byte{incoming, outgoing}[side] for index, fact := range facts { if filepath.Dir(fact.Path) != directory { t.Fatal("recording escaped directory") } raw, err := os.ReadFile(fact.Path) if err != nil { t.Fatal(err) } sum := sha256.Sum256(raw) if fact.SHA256 != hex.EncodeToString(sum[:]) || fact.Bytes != len(raw) || fact.DurationMS != int64(len(expected[index]))*1000/32000 { t.Fatal("recording fact does not match content") } if string(raw[:4]) != "RIFF" || string(raw[8:12]) != "WAVE" || string(raw[36:40]) != "data" { t.Fatal("invalid WAV header") } if binary.LittleEndian.Uint32(raw[24:28]) != 16000 || binary.LittleEndian.Uint32(raw[40:44]) != uint32(len(expected[index])) || !bytes.Equal(raw[44:], expected[index]) { t.Fatal("incorrect WAV format or payload") } info, err := os.Stat(fact.Path) if err != nil { t.Fatal(err) } if info.Mode().Perm() != 0600 { t.Fatal("recording is not restricted") } } } if in[0].Segment != "inbound_turn_01" || out[0].Segment != "outbound_segment_00" { t.Fatal("segment identities changed") } } func TestRecordingFailuresAreExplicit(t *testing.T) { directory := t.TempDir() if err := writeWAV(filepath.Join(directory, "odd.wav"), []byte{1}, 16000); err == nil { t.Fatal("odd PCM accepted") } blocker := filepath.Join(directory, "file") if err := os.WriteFile(blocker, []byte("blocked"), 0600); err != nil { t.Fatal(err) } if _, _, err := persistConversationRecordings(filepath.Join(blocker, "child"), "channel", callflow.Result{InboundTurns: [][]byte{{0, 0}}}); err == nil { t.Fatal("directory creation error hidden") } for _, flow := range []callflow.Result{{InboundTurns: [][]byte{{1}}}, {OutboundTurns: [][]byte{{1}}}} { if _, _, err := persistConversationRecordings(directory, "channel", flow); err == nil { t.Fatal("invalid segment accepted") } } for _, dir := range []string{"", directory} { in, out, err := persistConversationRecordings(dir, "empty", callflow.Result{}) if err != nil || len(in) != 0 || len(out) != 0 { t.Fatal("empty recording produced facts") } } if _, _, err := fileDigestAndSize(filepath.Join(directory, "missing.wav")); err == nil { t.Fatal("missing recording silently returned a checksum") } }