1use super::SseStream;
5#[cfg(doc)]
6use crate::framing::ChatFrameDecoder;
7use crate::framing::FramedChatStream;
8
9pub type SseChatStream<S, D> = FramedChatStream<SseStream<S>, D>;
81
82#[cfg(all(test, not(target_arch = "wasm32")))]
83mod tests {
84 use std::sync::{Arc, Mutex};
85
86 use allama::{ChatStream, Error, StreamErrorKind};
87 use futures_util::StreamExt;
88
89 use super::*;
90 use crate::framing::ChatFrameDecoder;
91 use crate::sse::{SseError, SseEvent};
92 use crate::{TransportError, TransportErrorKind};
93 use allama::Result;
94 use allama::StreamEvent;
95 use futures_core::Stream;
96
97 #[derive(Debug, Default)]
99 struct Calls {
100 events: Vec<String>,
101 ends: usize,
102 fails: Vec<Error>,
103 }
104
105 struct Scripted {
108 calls: Arc<Mutex<Calls>>,
109 script: fn(&str) -> Vec<Result<StreamEvent>>,
110 end: Vec<Result<StreamEvent>>,
111 done_after: Option<&'static str>,
112 done: bool,
113 }
114
115 impl Scripted {
116 fn new(script: fn(&str) -> Vec<Result<StreamEvent>>) -> (Self, Arc<Mutex<Calls>>) {
117 let calls = Arc::default();
118 let decoder = Self {
119 calls: Arc::clone(&calls),
120 script,
121 end: Vec::new(),
122 done_after: None,
123 done: false,
124 };
125 (decoder, calls)
126 }
127 }
128
129 impl ChatFrameDecoder<SseEvent> for Scripted {
130 fn frame(&mut self, event: SseEvent) -> Vec<Result<StreamEvent>> {
131 self.calls.lock().unwrap().events.push(event.data.clone());
132 self.done |= self.done_after == Some(event.data.as_str());
133 (self.script)(&event.data)
134 }
135
136 fn end(&mut self) -> Vec<Result<StreamEvent>> {
137 self.calls.lock().unwrap().ends += 1;
138 std::mem::take(&mut self.end)
139 }
140
141 fn fail(&mut self, error: Error) -> Vec<Result<StreamEvent>> {
142 self.calls.lock().unwrap().fails.push(error.clone());
143 vec![Err(error)]
144 }
145
146 fn is_done(&self) -> bool {
147 self.done
148 }
149 }
150
151 fn delta(data: &str) -> Vec<Result<StreamEvent>> {
152 vec![Ok(StreamEvent::text_delta(0, data))]
153 }
154
155 fn body(
157 chunks: Vec<std::result::Result<&'static str, TransportError>>,
158 polls: &Arc<Mutex<usize>>,
159 ) -> impl Stream<Item = std::result::Result<&'static str, TransportError>> + Send + Unpin + 'static
160 {
161 let polls = Arc::clone(polls);
162 futures_util::stream::iter(chunks).inspect(move |_| *polls.lock().unwrap() += 1)
163 }
164
165 async fn run<D: ChatFrameDecoder<SseEvent>>(
166 chunks: Vec<std::result::Result<&'static str, TransportError>>,
167 decoder: D,
168 ) -> (Vec<Result<StreamEvent>>, usize) {
169 let polls = Arc::default();
170 let stream = SseChatStream::new(SseStream::new(body(chunks, &polls)), decoder);
171 let items: Vec<_> = stream.collect().await;
172 let polls = *polls.lock().unwrap();
173 (items, polls)
174 }
175
176 #[tokio::test]
177 async fn end_runs_once_when_the_body_ends() {
178 let (mut decoder, calls) = Scripted::new(delta);
179 decoder.end = vec![Ok(StreamEvent::ResponseStop)];
180 let (items, _) = run(vec![Ok("data: a\n\ndata: b\n\n")], decoder).await;
181 assert_eq!(items.len(), 3);
182 assert!(matches!(items[2], Ok(StreamEvent::ResponseStop)));
183 let calls = calls.lock().unwrap();
184 assert_eq!(calls.events, ["a", "b"]);
185 assert_eq!(calls.ends, 1);
186 assert!(calls.fails.is_empty());
187 }
188
189 #[tokio::test]
190 async fn an_empty_end_terminates() {
191 let (decoder, calls) = Scripted::new(delta);
192 let (items, _) = run(vec![Ok("data: a\n\n")], decoder).await;
193 assert_eq!(items.len(), 1);
194 assert_eq!(calls.lock().unwrap().ends, 1);
195 }
196
197 #[tokio::test]
198 async fn fail_runs_once_when_the_body_fails_and_nothing_follows() {
199 let (decoder, calls) = Scripted::new(delta);
200 let (items, polls) = run(
201 vec![
202 Ok("data: a\n\n"),
203 Err(TransportError::new(TransportErrorKind::Body, "reset")),
204 Ok("data: b\n\n"),
205 ],
206 decoder,
207 )
208 .await;
209 assert_eq!(items.len(), 2);
210 let error = items[1].as_ref().unwrap_err();
211 assert!(matches!(
212 error,
213 Error::Stream {
214 kind: StreamErrorKind::Transport,
215 ..
216 }
217 ));
218 assert_eq!(error.upstream_message(), Some("reset"));
219 assert_eq!(polls, 2, "the body is not polled after it failed");
220 let calls = calls.lock().unwrap();
221 assert_eq!(calls.fails.len(), 1);
222 assert_eq!(calls.ends, 0);
223 }
224
225 #[tokio::test]
226 async fn a_timeout_stays_a_timeout() {
227 let (decoder, _) = Scripted::new(delta);
228 let (items, _) = run(
229 vec![Err(TransportError::new(
230 TransportErrorKind::Timeout,
231 "idle",
232 ))],
233 decoder,
234 )
235 .await;
236 assert!(matches!(items[..], [Err(Error::Timeout { .. })]));
237 }
238
239 #[tokio::test]
240 async fn the_stream_ends_after_a_decoder_error() {
241 let (decoder, calls) = Scripted::new(|data| match data {
242 "bad" => vec![
243 Ok(StreamEvent::text_delta(0, "before")),
244 Err(Error::stream_decode("bad")),
245 Ok(StreamEvent::text_delta(0, "dropped")),
246 ],
247 data => delta(data),
248 });
249 let (items, polls) = run(
250 vec![
251 Ok("data: a\n\n"),
252 Ok("data: bad\n\n"),
253 Ok("data: c\n\n"),
254 Ok("data: d\n\n"),
255 ],
256 decoder,
257 )
258 .await;
259 assert_eq!(items.len(), 3, "{items:?}");
260 assert!(items[2].is_err());
261 assert_eq!(polls, 2, "the body is not polled after the error");
262 let calls = calls.lock().unwrap();
263 assert_eq!(calls.events, ["a", "bad"]);
264 assert_eq!(calls.ends, 0);
265 assert!(calls.fails.is_empty());
266 }
267
268 #[tokio::test]
269 async fn the_stream_ends_once_the_decoder_is_done() {
270 let (mut decoder, calls) = Scripted::new(delta);
271 decoder.done_after = Some("last");
272 let (items, polls) = run(
273 vec![
274 Ok("data: a\n\ndata: last\n\n"),
275 Ok("data: ignored\n\n"),
276 Err(TransportError::new(TransportErrorKind::Body, "never read")),
277 ],
278 decoder,
279 )
280 .await;
281 assert_eq!(items.len(), 2);
282 assert_eq!(polls, 1);
283 let calls = calls.lock().unwrap();
284 assert_eq!(calls.events, ["a", "last"]);
285 assert_eq!(calls.ends, 0);
286 assert!(calls.fails.is_empty());
287 }
288
289 #[tokio::test]
290 async fn sse_failures_are_classified() {
291 let polls = Arc::default();
292 let invalid = futures_util::stream::iter([Ok::<_, TransportError>(&b"data: \xFF\n\n"[..])]);
293 let (decoder, _) = Scripted::new(delta);
294 let items: Vec<_> = SseChatStream::new(SseStream::new(invalid), decoder)
295 .collect()
296 .await;
297 let error = items[0].as_ref().unwrap_err();
298 assert!(matches!(
299 error,
300 Error::Stream {
301 kind: StreamErrorKind::Decode,
302 ..
303 }
304 ));
305 assert!(!error.is_retryable());
306 assert_eq!(error.message_origin(), allama::MessageOrigin::Authored);
307
308 let (decoder, _) = Scripted::new(delta);
309 let events =
310 SseStream::new(body(vec![Ok("data: 0123456789abcdef!\n\n")], &polls)).max_event_size(8);
311 let items: Vec<_> = SseChatStream::new(events, decoder).collect().await;
312 assert!(matches!(
313 items[..],
314 [Err(Error::Stream {
315 kind: StreamErrorKind::Protocol,
316 ..
317 })]
318 ));
319 }
320
321 #[tokio::test]
322 async fn an_error_map_classifies_other_bodies() {
323 let body = futures_util::stream::iter([
324 Ok::<_, String>("data: a\n\n"),
325 Err("platform failure".to_owned()),
326 ]);
327 let (decoder, _) = Scripted::new(delta);
328 let stream =
329 SseChatStream::with_error_map(SseStream::new(body), decoder, |error| match error {
330 SseError::Transport(text) => Error::stream(StreamErrorKind::Transport, "failed")
331 .authored()
332 .with_upstream_message(text),
333 other => Error::stream_decode(other.to_string()).authored(),
334 });
335 let items: Vec<_> = stream.collect().await;
336 assert_eq!(
337 items[1].as_ref().unwrap_err().upstream_message(),
338 Some("platform failure")
339 );
340 }
341
342 #[test]
343 fn the_stream_is_send() {
344 fn send<T: Send + 'static>() {}
345 send::<SseChatStream<crate::Body, Scripted>>();
346 let (decoder, _) = Scripted::new(delta);
347 let events = SseStream::new(futures_util::stream::empty::<
348 std::result::Result<bytes::Bytes, TransportError>,
349 >());
350 let _: ChatStream = Box::pin(SseChatStream::new(events, decoder));
351 }
352}