| 206 | } |
| 207 | |
| 208 | func (ms *mockUpstream) writeSSE(w http.ResponseWriter, data []byte) { |
| 209 | ms.t.Helper() |
| 210 | |
| 211 | w.Header().Set("Content-Type", "text/event-stream") |
| 212 | w.Header().Set("Cache-Control", "no-cache") |
| 213 | w.Header().Set("Connection", "keep-alive") |
| 214 | |
| 215 | flusher, ok := w.(http.Flusher) |
| 216 | if !ok { |
| 217 | http.Error(w, "streaming unsupported", http.StatusInternalServerError) |
| 218 | return |
| 219 | } |
| 220 | |
| 221 | // Write line-by-line to simulate SSE events arriving incrementally. |
| 222 | // SplitAfter keeps the line endings so fixture bytes (LF or CRLF) replay verbatim. |
| 223 | for _, line := range bytes.SplitAfter(data, []byte("\n")) { |
| 224 | if len(line) == 0 { |
| 225 | continue |
| 226 | } |
| 227 | if _, err := w.Write(line); err != nil { |
| 228 | if eventstream.IsConnError(err) { |
| 229 | return // client disconnected, stop writing |
| 230 | } |
| 231 | require.NoError(ms.t, err) |
| 232 | } |
| 233 | flusher.Flush() |
| 234 | } |
| 235 | } |
| 236 | |
| 237 | // isRawHTTPResponse returns true if data starts with "HTTP/", indicating |
| 238 | // it contains a complete HTTP response (status line + headers + body) rather |