From cce5655f2f88e6541ba6f70cb68d183beba19ac9 Mon Sep 17 00:00:00 2001
From: cai <cai@nbcai.cc>
Date: Sat, 08 Aug 2026 18:42:37 +0800
Subject: [PATCH] test: cover per-session participant metadata refresh

---
 src/asr_realtime.rs |   87 +++++++++++++++++++++++++++++++++++++++++++
 src/main.rs         |   15 +------
 2 files changed, 89 insertions(+), 13 deletions(-)

diff --git a/src/asr_realtime.rs b/src/asr_realtime.rs
index 9358d23..c1afd25 100644
--- a/src/asr_realtime.rs
+++ b/src/asr_realtime.rs
@@ -93,6 +93,33 @@
 }
 
 impl RealtimeAsrUpload {
+    pub(crate) fn start_with_participant_attributes<F>(
+        http: Client,
+        config: RealtimeAsrConfig,
+        call_id: &str,
+        trace_id: &str,
+        turn_id: &str,
+        initial_samples_48k: &[i16],
+        read_attributes: F,
+    ) -> Result<Self>
+    where
+        F: FnOnce() -> HashMap<String, String>,
+    {
+        let metadata = AudioIngressMetadata::from_participant(&read_attributes())
+            .map_err(|reason| anyhow!(reason))
+            .ok()
+            .flatten();
+        Self::start(
+            http,
+            config,
+            call_id,
+            trace_id,
+            turn_id,
+            initial_samples_48k,
+            metadata.as_ref(),
+        )
+    }
+
     pub(crate) fn start(
         http: Client,
         config: RealtimeAsrConfig,
@@ -540,6 +567,66 @@
         );
     }
 
+    #[tokio::test]
+    async fn production_session_boundary_reads_updated_attributes_per_session() {
+        let mut attributes = HashMap::new();
+        attributes.insert(
+            "inputSourceCategory".to_string(),
+            "controlled_fixture".to_string(),
+        );
+        attributes.insert(
+            "clientFixtureSequence".to_string(),
+            "fixture-01".to_string(),
+        );
+        let (url1, captured1, server1) =
+            spawn_http_fixture(json!({"code": 0, "data": {"status": "cancelled"}}).to_string());
+        let upload1 = RealtimeAsrUpload::start_with_participant_attributes(
+            Client::new(),
+            fixture_config(url1),
+            "call-001",
+            "trace-001",
+            "turn-0001",
+            &vec![1; 9_600],
+            || attributes.clone(),
+        )
+        .expect("session one");
+        upload1.cancel("test").await;
+        let request1 = captured1.recv().expect("session one request");
+        server1.join().expect("session one server");
+        assert!(
+            request1
+                .body
+                .contains("\"clientFixtureSequence\":\"fixture-01\"")
+        );
+        assert!(!request1.body.contains("fixture-02"));
+
+        attributes.insert(
+            "clientFixtureSequence".to_string(),
+            "fixture-02".to_string(),
+        );
+        let (url2, captured2, server2) =
+            spawn_http_fixture(json!({"code": 0, "data": {"status": "cancelled"}}).to_string());
+        let upload2 = RealtimeAsrUpload::start_with_participant_attributes(
+            Client::new(),
+            fixture_config(url2),
+            "call-001",
+            "trace-001",
+            "turn-0002",
+            &vec![1; 9_600],
+            || attributes.clone(),
+        )
+        .expect("session two");
+        upload2.cancel("test").await;
+        let request2 = captured2.recv().expect("session two request");
+        server2.join().expect("session two server");
+        assert!(
+            request2
+                .body
+                .contains("\"clientFixtureSequence\":\"fixture-02\"")
+        );
+        assert!(!request2.body.contains("fixture-01"));
+    }
+
     #[test]
     fn maximum_audio_chunk_stays_within_java_line_limit() {
         let line = audio_chunk_line(1, &vec![0; 8_000]).expect("maximum chunk line");
diff --git a/src/main.rs b/src/main.rs
index eed3859..d357e4a 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -3043,25 +3043,14 @@
 
                     if !was_in_speech && is_in_speech {
                         let turn_id = format!("turn-{:04}", vad.turn_index);
-                        let ingress_metadata = match AudioIngressMetadata::from_participant(
-                            &participant.attributes(),
-                        ) {
-                            Ok(value) => value,
-                            Err(reason) => {
-                                warn!(call_id = %call_id, trace_id = %trace_id,
-                                    turn_id = %turn_id, metadata_status = "invalid", reason = reason,
-                                    "runtime helper ignored invalid audio ingress metadata");
-                                None
-                            }
-                        };
-                        match RealtimeAsrUpload::start(
+                        match RealtimeAsrUpload::start_with_participant_attributes(
                             http.clone(),
                             turn_bridge_config.realtime_asr_config(),
                             &call_id,
                             &trace_id,
                             &turn_id,
                             &vad.speech_samples,
-                            ingress_metadata.as_ref(),
+                            || participant.attributes(),
                         ) {
                             Ok(upload) => {
                                 info!(

--
Gitblit v1.9.3