github.com/Jeffail/benthos/v3@v3.65.0/lib/output/drop_on_test.go (about) 1 package output 2 3 import ( 4 "fmt" 5 "net/http" 6 "net/http/httptest" 7 "strings" 8 "sync" 9 "testing" 10 "time" 11 12 "github.com/Jeffail/benthos/v3/lib/log" 13 "github.com/Jeffail/benthos/v3/lib/message" 14 "github.com/Jeffail/benthos/v3/lib/metrics" 15 "github.com/Jeffail/benthos/v3/lib/types" 16 "github.com/gorilla/websocket" 17 "github.com/stretchr/testify/assert" 18 "github.com/stretchr/testify/require" 19 ) 20 21 func TestDropOnNothing(t *testing.T) { 22 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 23 http.Error(w, "test error", http.StatusForbidden) 24 })) 25 t.Cleanup(func() { 26 ts.Close() 27 }) 28 29 childConf := NewConfig() 30 childConf.Type = TypeHTTPClient 31 childConf.HTTPClient.URL = ts.URL 32 childConf.HTTPClient.DropOn = []int{http.StatusForbidden} 33 34 child, err := New(childConf, nil, log.Noop(), metrics.Noop()) 35 require.NoError(t, err) 36 t.Cleanup(func() { 37 child.CloseAsync() 38 assert.NoError(t, child.WaitForClose(time.Second*5)) 39 }) 40 41 dropConf := NewDropOnConfig() 42 dropConf.Error = false 43 44 d, err := newDropOn(dropConf.DropOnConditions, child, log.Noop(), metrics.Noop()) 45 require.NoError(t, err) 46 t.Cleanup(func() { 47 d.CloseAsync() 48 assert.NoError(t, d.WaitForClose(time.Second*5)) 49 }) 50 51 tChan := make(chan types.Transaction) 52 rChan := make(chan types.Response) 53 54 require.NoError(t, d.Consume(tChan)) 55 56 select { 57 case tChan <- types.NewTransaction(message.New([][]byte{[]byte("foobar")}), rChan): 58 case <-time.After(time.Second): 59 t.Fatal("timed out") 60 } 61 62 var res types.Response 63 select { 64 case res = <-rChan: 65 case <-time.After(time.Second): 66 t.Fatal("timed out") 67 } 68 69 assert.EqualError(t, res.Error(), fmt.Sprintf("%s: HTTP request returned unexpected response code (403): 403 Forbidden, Error: test error", ts.URL)) 70 } 71 72 func TestDropOnError(t *testing.T) { 73 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 74 http.Error(w, "test error", http.StatusForbidden) 75 })) 76 t.Cleanup(func() { 77 ts.Close() 78 }) 79 80 childConf := NewConfig() 81 childConf.Type = TypeHTTPClient 82 childConf.HTTPClient.URL = ts.URL 83 childConf.HTTPClient.DropOn = []int{http.StatusForbidden} 84 85 child, err := New(childConf, nil, log.Noop(), metrics.Noop()) 86 require.NoError(t, err) 87 t.Cleanup(func() { 88 child.CloseAsync() 89 assert.NoError(t, child.WaitForClose(time.Second*5)) 90 }) 91 92 dropConf := NewDropOnConfig() 93 dropConf.Error = true 94 95 d, err := newDropOn(dropConf.DropOnConditions, child, log.Noop(), metrics.Noop()) 96 require.NoError(t, err) 97 t.Cleanup(func() { 98 d.CloseAsync() 99 assert.NoError(t, d.WaitForClose(time.Second*5)) 100 }) 101 102 tChan := make(chan types.Transaction) 103 rChan := make(chan types.Response) 104 105 require.NoError(t, d.Consume(tChan)) 106 107 select { 108 case tChan <- types.NewTransaction(message.New([][]byte{[]byte("foobar")}), rChan): 109 case <-time.After(time.Second): 110 t.Fatal("timed out") 111 } 112 113 var res types.Response 114 select { 115 case res = <-rChan: 116 case <-time.After(time.Second): 117 t.Fatal("timed out") 118 } 119 120 assert.NoError(t, res.Error()) 121 } 122 123 func TestDropOnBackpressureWithErrors(t *testing.T) { 124 // Skip this test in most runs as it relies on awkward timers. 125 t.Skip() 126 127 var wsMut sync.Mutex 128 var wsReceived []string 129 var wsAllow bool 130 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 131 wsMut.Lock() 132 allow := wsAllow 133 wsMut.Unlock() 134 if !allow { 135 http.Error(w, "nope", http.StatusForbidden) 136 return 137 } 138 139 upgrader := websocket.Upgrader{} 140 141 ws, err := upgrader.Upgrade(w, r, nil) 142 if err != nil { 143 return 144 } 145 defer ws.Close() 146 147 for { 148 _, actBytes, err := ws.ReadMessage() 149 if err != nil { 150 return 151 } 152 wsMut.Lock() 153 wsReceived = append(wsReceived, string(actBytes)) 154 wsMut.Unlock() 155 } 156 })) 157 t.Cleanup(func() { 158 ts.Close() 159 }) 160 161 childConf := NewConfig() 162 childConf.Type = TypeWebsocket 163 childConf.Websocket.URL = "ws://" + strings.TrimPrefix(ts.URL, "http://") 164 165 child, err := New(childConf, nil, log.Noop(), metrics.Noop()) 166 require.NoError(t, err) 167 t.Cleanup(func() { 168 child.CloseAsync() 169 assert.NoError(t, child.WaitForClose(time.Second*5)) 170 }) 171 172 dropConf := NewDropOnConfig() 173 dropConf.BackPressure = "100ms" 174 175 d, err := newDropOn(dropConf.DropOnConditions, child, log.Noop(), metrics.Noop()) 176 require.NoError(t, err) 177 t.Cleanup(func() { 178 d.CloseAsync() 179 assert.NoError(t, d.WaitForClose(time.Second*5)) 180 }) 181 182 tChan := make(chan types.Transaction) 183 rChan := make(chan types.Response) 184 185 require.NoError(t, d.Consume(tChan)) 186 187 sendAndGet := func(msg string, expErr string) { 188 t.Helper() 189 190 select { 191 case tChan <- types.NewTransaction(message.New([][]byte{[]byte(msg)}), rChan): 192 case <-time.After(time.Second): 193 t.Fatal("timed out") 194 } 195 196 var res types.Response 197 select { 198 case res = <-rChan: 199 case <-time.After(time.Second): 200 t.Fatal("timed out") 201 } 202 203 if expErr == "" { 204 assert.NoError(t, res.Error()) 205 } else { 206 assert.EqualError(t, res.Error(), expErr) 207 } 208 } 209 210 sendAndGet("first", "experienced back pressure beyond: 100ms") 211 sendAndGet("second", "experienced back pressure beyond: 100ms") 212 wsMut.Lock() 213 wsAllow = true 214 wsMut.Unlock() 215 <-time.After(time.Second) 216 217 sendAndGet("third", "") 218 sendAndGet("fourth", "") 219 220 <-time.After(time.Second) 221 wsMut.Lock() 222 assert.Equal(t, []string{"third", "fourth"}, wsReceived) 223 wsMut.Unlock() 224 } 225 226 func TestDropOnDisconnectBackpressureNoErrors(t *testing.T) { 227 // Skip this test in most runs as it relies on awkward timers. 228 t.Skip() 229 230 var wsReceived []string 231 var ws *websocket.Conn 232 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 233 upgrader := websocket.Upgrader{} 234 235 var err error 236 if ws, err = upgrader.Upgrade(w, r, nil); err != nil { 237 return 238 } 239 defer ws.Close() 240 241 for { 242 _, actBytes, err := ws.ReadMessage() 243 if err != nil { 244 return 245 } 246 wsReceived = append(wsReceived, string(actBytes)) 247 } 248 })) 249 t.Cleanup(func() { 250 ts.Close() 251 }) 252 253 childConf := NewConfig() 254 childConf.Type = TypeWebsocket 255 childConf.Websocket.URL = "ws://" + strings.TrimPrefix(ts.URL, "http://") 256 257 child, err := New(childConf, nil, log.Noop(), metrics.Noop()) 258 require.NoError(t, err) 259 t.Cleanup(func() { 260 child.CloseAsync() 261 assert.NoError(t, child.WaitForClose(time.Second*5)) 262 }) 263 264 dropConf := NewDropOnConfig() 265 dropConf.Error = true 266 dropConf.BackPressure = "100ms" 267 268 d, err := newDropOn(dropConf.DropOnConditions, child, log.Noop(), metrics.Noop()) 269 require.NoError(t, err) 270 t.Cleanup(func() { 271 d.CloseAsync() 272 assert.NoError(t, d.WaitForClose(time.Second*5)) 273 }) 274 275 tChan := make(chan types.Transaction) 276 rChan := make(chan types.Response) 277 278 require.NoError(t, d.Consume(tChan)) 279 280 sendAndGet := func(msg string, expErr string) { 281 t.Helper() 282 283 select { 284 case tChan <- types.NewTransaction(message.New([][]byte{[]byte(msg)}), rChan): 285 case <-time.After(time.Second): 286 t.Fatal("timed out") 287 } 288 289 var res types.Response 290 select { 291 case res = <-rChan: 292 case <-time.After(time.Second): 293 t.Fatal("timed out") 294 } 295 296 if expErr == "" { 297 assert.NoError(t, res.Error()) 298 } else { 299 assert.EqualError(t, res.Error(), expErr) 300 } 301 } 302 303 sendAndGet("first", "") 304 sendAndGet("second", "") 305 306 ts.Close() 307 ws.Close() 308 <-time.After(time.Second) 309 310 sendAndGet("third", "") 311 sendAndGet("fourth", "") 312 313 <-time.After(time.Second) 314 315 assert.Equal(t, []string{"first", "second"}, wsReceived) 316 }