github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/engine/access/rest/routes/test_helpers.go (about)

     1  package routes
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"fmt"
     7  	"io"
     8  	"net"
     9  	"net/http"
    10  	"net/http/httptest"
    11  	"strings"
    12  	"testing"
    13  	"time"
    14  
    15  	"github.com/stretchr/testify/require"
    16  
    17  	"github.com/onflow/flow-go/access"
    18  	"github.com/onflow/flow-go/access/mock"
    19  	"github.com/onflow/flow-go/engine/access/state_stream"
    20  	"github.com/onflow/flow-go/engine/access/state_stream/backend"
    21  	"github.com/onflow/flow-go/engine/access/subscription"
    22  	"github.com/onflow/flow-go/model/flow"
    23  	"github.com/onflow/flow-go/module/metrics"
    24  	"github.com/onflow/flow-go/utils/unittest"
    25  )
    26  
    27  const (
    28  	ExpandableFieldPayload      = "payload"
    29  	ExpandableExecutionResult   = "execution_result"
    30  	sealedHeightQueryParam      = "sealed"
    31  	finalHeightQueryParam       = "final"
    32  	startHeightQueryParam       = "start_height"
    33  	endHeightQueryParam         = "end_height"
    34  	heightQueryParam            = "height"
    35  	startBlockIdQueryParam      = "start_block_id"
    36  	eventTypesQueryParams       = "event_types"
    37  	addressesQueryParams        = "addresses"
    38  	contractsQueryParams        = "contracts"
    39  	heartbeatIntervalQueryParam = "heartbeat_interval"
    40  )
    41  
    42  // fakeNetConn implements a mocked ws connection that can be injected in testing logic.
    43  type fakeNetConn struct {
    44  	io.Writer
    45  	closed chan struct{}
    46  }
    47  
    48  var _ net.Conn = (*fakeNetConn)(nil)
    49  
    50  // Close closes the fakeNetConn and signals its closure by closing the "closed" channel.
    51  func (c fakeNetConn) Close() error {
    52  	select {
    53  	case <-c.closed:
    54  	default:
    55  		close(c.closed)
    56  	}
    57  	return nil
    58  }
    59  
    60  func (c fakeNetConn) LocalAddr() net.Addr                { return localAddr }
    61  func (c fakeNetConn) RemoteAddr() net.Addr               { return remoteAddr }
    62  func (c fakeNetConn) SetDeadline(t time.Time) error      { return nil }
    63  func (c fakeNetConn) SetReadDeadline(t time.Time) error  { return nil }
    64  func (c fakeNetConn) SetWriteDeadline(t time.Time) error { return nil }
    65  func (c fakeNetConn) Read(p []byte) (n int, err error) {
    66  	<-c.closed
    67  	return 0, fmt.Errorf("closed")
    68  }
    69  
    70  type fakeAddr int
    71  
    72  var (
    73  	localAddr  = fakeAddr(1)
    74  	remoteAddr = fakeAddr(2)
    75  )
    76  
    77  func (a fakeAddr) Network() string {
    78  	return "net"
    79  }
    80  
    81  func (a fakeAddr) String() string {
    82  	return "str"
    83  }
    84  
    85  // testHijackResponseRecorder is a custom ResponseRecorder that implements the http.Hijacker interface
    86  // for testing WebSocket connections and hijacking.
    87  type testHijackResponseRecorder struct {
    88  	*httptest.ResponseRecorder
    89  	closed       chan struct{}
    90  	responseBuff *bytes.Buffer
    91  }
    92  
    93  var _ http.Hijacker = (*testHijackResponseRecorder)(nil)
    94  
    95  // Hijack implements the http.Hijacker interface by returning a fakeNetConn and a bufio.ReadWriter
    96  // that simulate a hijacked connection.
    97  func (w *testHijackResponseRecorder) Hijack() (net.Conn, *bufio.ReadWriter, error) {
    98  	br := bufio.NewReaderSize(strings.NewReader(""), subscription.DefaultSendBufferSize)
    99  	bw := bufio.NewWriterSize(&bytes.Buffer{}, subscription.DefaultSendBufferSize)
   100  	w.responseBuff = bytes.NewBuffer(make([]byte, 0))
   101  	w.closed = make(chan struct{}, 1)
   102  
   103  	return fakeNetConn{w.responseBuff, w.closed}, bufio.NewReadWriter(br, bw), nil
   104  }
   105  
   106  func (w *testHijackResponseRecorder) Close() error {
   107  	select {
   108  	case <-w.closed:
   109  	default:
   110  		close(w.closed)
   111  	}
   112  	return nil
   113  }
   114  
   115  // newTestHijackResponseRecorder creates a new instance of testHijackResponseRecorder.
   116  func newTestHijackResponseRecorder() *testHijackResponseRecorder {
   117  	return &testHijackResponseRecorder{
   118  		ResponseRecorder: httptest.NewRecorder(),
   119  	}
   120  }
   121  
   122  func executeRequest(req *http.Request, backend access.API) *httptest.ResponseRecorder {
   123  	router := NewRouterBuilder(
   124  		unittest.Logger(),
   125  		metrics.NewNoopCollector(),
   126  	).AddRestRoutes(
   127  		backend,
   128  		flow.Testnet.Chain(),
   129  	).Build()
   130  
   131  	rr := httptest.NewRecorder()
   132  	router.ServeHTTP(rr, req)
   133  	return rr
   134  }
   135  
   136  func executeWsRequest(req *http.Request, stateStreamApi state_stream.API, responseRecorder *testHijackResponseRecorder, chain flow.Chain) {
   137  	restCollector := metrics.NewNoopCollector()
   138  
   139  	config := backend.Config{
   140  		EventFilterConfig: state_stream.DefaultEventFilterConfig,
   141  		MaxGlobalStreams:  subscription.DefaultMaxGlobalStreams,
   142  		HeartbeatInterval: subscription.DefaultHeartbeatInterval,
   143  	}
   144  
   145  	router := NewRouterBuilder(unittest.Logger(), restCollector).AddWsRoutes(
   146  		stateStreamApi,
   147  		chain, config).Build()
   148  	router.ServeHTTP(responseRecorder, req)
   149  }
   150  
   151  func assertOKResponse(t *testing.T, req *http.Request, expectedRespBody string, backend *mock.API) {
   152  	assertResponse(t, req, http.StatusOK, expectedRespBody, backend)
   153  }
   154  
   155  func assertResponse(t *testing.T, req *http.Request, status int, expectedRespBody string, backend *mock.API) {
   156  	rr := executeRequest(req, backend)
   157  	actualResponseBody := rr.Body.String()
   158  	require.JSONEq(t,
   159  		expectedRespBody,
   160  		actualResponseBody,
   161  		fmt.Sprintf("Failed Request: %s\nExpected JSON:\n %s \nActual JSON:\n %s\n", req.URL, expectedRespBody, actualResponseBody),
   162  	)
   163  	require.Equal(t, status, rr.Code)
   164  }