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  }