@@ -16,146 +16,146 @@ def create_mock_event_source(data_chunks: list[bytes]) -> EventSource:
16
16
event_source = MagicMock (spec = EventSource )
17
17
response = AsyncMock ()
18
18
event_source .response = response
19
-
19
+
20
20
async def mock_aiter_bytes () -> AsyncIterator [bytes ]:
21
21
for chunk in data_chunks :
22
22
yield chunk
23
-
23
+
24
24
response .aiter_bytes = mock_aiter_bytes
25
25
return event_source
26
26
27
27
28
28
async def test_compliant_aiter_sse_handles_unicode_line_separators ():
29
29
"""Test that compliant_aiter_sse correctly handles U+2028 and U+2029 characters."""
30
-
30
+
31
31
# Simulate SSE data with U+2028 in JSON content
32
32
# The server sends: event: message\ndata: {"text":"Hello\u2028World"}\n\n
33
33
test_data = [
34
- b' event: message\n ' ,
34
+ b" event: message\n " ,
35
35
b'data: {"text":"Hello' ,
36
- b' \xe2 \x80 \xa8 ' , # UTF-8 encoding of U+2028
36
+ b" \xe2 \x80 \xa8 " , # UTF-8 encoding of U+2028
37
37
b'World"}\n ' ,
38
- b' \n ' ,
38
+ b" \n " ,
39
39
]
40
-
40
+
41
41
event_source = create_mock_event_source (test_data )
42
-
42
+
43
43
# Collect events
44
44
events = [event async for event in compliant_aiter_sse (event_source )]
45
-
45
+
46
46
# Should receive one message event
47
47
assert len (events ) == 1
48
48
assert events [0 ].event == "message"
49
49
# The U+2028 should be preserved in the data
50
- assert ' \u2028 ' in events [0 ].data
50
+ assert " \u2028 " in events [0 ].data
51
51
assert events [0 ].data == '{"text":"Hello\u2028 World"}'
52
52
53
53
54
54
async def test_compliant_aiter_sse_handles_paragraph_separator ():
55
55
"""Test that compliant_aiter_sse correctly handles U+2029 (PARAGRAPH SEPARATOR)."""
56
-
56
+
57
57
# Simulate SSE data with U+2029
58
58
test_data = [
59
- b' event: test\n data: Line1' ,
60
- b' \xe2 \x80 \xa9 ' , # UTF-8 encoding of U+2029
61
- b' Line2\n \n ' ,
59
+ b" event: test\n data: Line1" ,
60
+ b" \xe2 \x80 \xa9 " , # UTF-8 encoding of U+2029
61
+ b" Line2\n \n " ,
62
62
]
63
-
63
+
64
64
event_source = create_mock_event_source (test_data )
65
-
65
+
66
66
events = [event async for event in compliant_aiter_sse (event_source )]
67
-
67
+
68
68
assert len (events ) == 1
69
69
assert events [0 ].event == "test"
70
70
# U+2029 should be preserved, not treated as a newline
71
- assert ' \u2029 ' in events [0 ].data
72
- assert events [0 ].data == ' Line1\u2029 Line2'
71
+ assert " \u2029 " in events [0 ].data
72
+ assert events [0 ].data == " Line1\u2029 Line2"
73
73
74
74
75
75
async def test_compliant_aiter_sse_handles_crlf ():
76
76
"""Test that compliant_aiter_sse correctly handles \\ r\\ n line endings."""
77
-
77
+
78
78
# Simulate SSE data with CRLF line endings
79
79
test_data = [
80
- b' event: message\r \n ' ,
81
- b' data: test data\r \n ' ,
82
- b' \r \n ' ,
80
+ b" event: message\r \n " ,
81
+ b" data: test data\r \n " ,
82
+ b" \r \n " ,
83
83
]
84
-
84
+
85
85
event_source = create_mock_event_source (test_data )
86
-
86
+
87
87
events = [event async for event in compliant_aiter_sse (event_source )]
88
-
88
+
89
89
assert len (events ) == 1
90
90
assert events [0 ].event == "message"
91
91
assert events [0 ].data == "test data"
92
92
93
93
94
94
async def test_compliant_aiter_sse_handles_split_utf8 ():
95
95
"""Test that compliant_aiter_sse handles UTF-8 characters split across chunks."""
96
-
96
+
97
97
# Split a UTF-8 emoji (🎉 = \xf0\x9f\x8e\x89) across chunks
98
98
test_data = [
99
- b' event: message\n ' ,
100
- b' data: Party ' ,
101
- b' \xf0 \x9f ' , # First half of emoji
102
- b' \x8e \x89 ' , # Second half of emoji
103
- b' time!\n \n ' ,
99
+ b" event: message\n " ,
100
+ b" data: Party " ,
101
+ b" \xf0 \x9f " , # First half of emoji
102
+ b" \x8e \x89 " , # Second half of emoji
103
+ b" time!\n \n " ,
104
104
]
105
-
105
+
106
106
event_source = create_mock_event_source (test_data )
107
-
107
+
108
108
events = [event async for event in compliant_aiter_sse (event_source )]
109
-
109
+
110
110
assert len (events ) == 1
111
111
assert events [0 ].event == "message"
112
112
assert events [0 ].data == "Party 🎉 time!"
113
113
114
114
115
115
async def test_compliant_aiter_sse_handles_multiple_events ():
116
116
"""Test that compliant_aiter_sse correctly handles multiple SSE events."""
117
-
117
+
118
118
# Multiple events with problematic Unicode
119
119
test_data = [
120
- b' event: first\n data: Hello\xe2 \x80 \xa8 World\n \n ' ,
121
- b' event: second\n data: Test\xe2 \x80 \xa9 Data\n \n ' ,
122
- b' data: No event name\n \n ' ,
120
+ b" event: first\n data: Hello\xe2 \x80 \xa8 World\n \n " ,
121
+ b" event: second\n data: Test\xe2 \x80 \xa9 Data\n \n " ,
122
+ b" data: No event name\n \n " ,
123
123
]
124
-
124
+
125
125
event_source = create_mock_event_source (test_data )
126
-
126
+
127
127
events = [event async for event in compliant_aiter_sse (event_source )]
128
-
128
+
129
129
assert len (events ) == 3
130
-
130
+
131
131
assert events [0 ].event == "first"
132
- assert ' \u2028 ' in events [0 ].data
133
-
132
+ assert " \u2028 " in events [0 ].data
133
+
134
134
assert events [1 ].event == "second"
135
- assert ' \u2029 ' in events [1 ].data
136
-
135
+ assert " \u2029 " in events [1 ].data
136
+
137
137
# Default event type is "message"
138
138
assert events [2 ].event == "message"
139
139
assert events [2 ].data == "No event name"
140
140
141
141
142
142
async def test_compliant_aiter_sse_handles_split_crlf ():
143
143
"""Test that \r at end of chunk followed by \n in next chunk is treated as one newline."""
144
-
144
+
145
145
# Test case where \r is at the end of one chunk and \n starts the next
146
146
# This should be treated as a single CRLF line ending, not two separate newlines
147
147
test_data = [
148
- b' event: test\r ' , # \r at end of chunk
149
- b' \n data: line1\r ' , # \n at start of next chunk, then another \r at end
150
- b' \n data: line2\n \n ' , # \n at start, completing the CRLF
148
+ b" event: test\r " , # \r at end of chunk
149
+ b" \n data: line1\r " , # \n at start of next chunk, then another \r at end
150
+ b" \n data: line2\n \n " , # \n at start, completing the CRLF
151
151
]
152
-
152
+
153
153
event_source = create_mock_event_source (test_data )
154
-
154
+
155
155
events = [event async for event in compliant_aiter_sse (event_source )]
156
-
156
+
157
157
# Should get exactly one event with both data lines
158
158
assert len (events ) == 1
159
159
assert events [0 ].event == "test"
160
160
# The SSE decoder concatenates multiple data fields with \n
161
- assert events [0 ].data == "line1\n line2"
161
+ assert events [0 ].data == "line1\n line2"
0 commit comments