github.com/lmittmann/w3@v0.20.0/rpctest/server.go (about)

     1  // Package rpctest provides utilities for testing RPC methods.
     2  package rpctest
     3  
     4  import (
     5  	"bufio"
     6  	"bytes"
     7  	"io"
     8  	"net/http"
     9  	"net/http/httptest"
    10  	"os"
    11  	"sync"
    12  	"testing"
    13  )
    14  
    15  // Server is a fake RPC endpoint that responds only to a single requests that
    16  // is defined in a golden-file.
    17  //
    18  // Request golden-files have the following format to define a single request
    19  // and the corresponding response:
    20  //
    21  //	// Comments and empty lines will be ignored.
    22  //	// Request starts with ">".
    23  //	> {"jsonrpc":"2.0","id":1,"method":"eth_chainId"}
    24  //	// Response starts with "<".
    25  //	< {"jsonrpc":"2.0","id":1,"result":"0x1"}
    26  type Server struct {
    27  	t *testing.T
    28  
    29  	reader   io.Reader
    30  	readOnce sync.Once
    31  	in       []byte
    32  	out      []byte
    33  
    34  	httptestSrv *httptest.Server
    35  }
    36  
    37  // NewServer returns a new instance of Server that serves the golden-file from
    38  // Reader r.
    39  func NewServer(t *testing.T, r io.Reader) *Server {
    40  	srv := &Server{t: t, reader: r}
    41  	httptestSrv := httptest.NewServer(srv)
    42  
    43  	srv.httptestSrv = httptestSrv
    44  	return srv
    45  }
    46  
    47  // NewFileServer returns a new instance of Server that serves the golden-file
    48  // from the given filename.
    49  func NewFileServer(t *testing.T, filename string) *Server {
    50  	f, err := os.Open(filename)
    51  	if err != nil {
    52  		t.Fatalf("Failed to open file: %v", err)
    53  	}
    54  
    55  	return NewServer(t, f)
    56  }
    57  
    58  func (srv *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    59  	srv.readOnce.Do(srv.readGolden)
    60  
    61  	// read body
    62  	body, err := io.ReadAll(r.Body)
    63  	if err != nil {
    64  		srv.t.Fatalf("Failed to read body: %v", err)
    65  	}
    66  
    67  	// check body
    68  	if !bytes.Equal(srv.in, body) {
    69  		srv.t.Fatalf("Invalid request body (-want, +got)\n-%s\n+%s", srv.in, body)
    70  	}
    71  
    72  	// respond
    73  	w.Header().Set("Content-Type", "application/json")
    74  	w.Write(srv.out)
    75  }
    76  
    77  // URL returns the servers RPC endpoint url.
    78  func (srv *Server) URL() string {
    79  	return srv.httptestSrv.URL
    80  }
    81  
    82  // Close shuts down the server.
    83  func (srv *Server) Close() {
    84  	srv.httptestSrv.Close()
    85  }
    86  
    87  func (srv *Server) readGolden() {
    88  	if rc, ok := srv.reader.(io.ReadCloser); ok {
    89  		defer rc.Close()
    90  	}
    91  
    92  	scan := bufio.NewScanner(srv.reader)
    93  	for scan.Scan() {
    94  		line := scan.Bytes()
    95  		if len(line) <= 0 {
    96  			continue // skip empty lines
    97  		}
    98  
    99  		switch line[0] {
   100  		case '>':
   101  			trimedLine := bytes.Trim(line, "> ")
   102  			srv.in = make([]byte, len(trimedLine))
   103  			copy(srv.in, trimedLine)
   104  		case '<':
   105  			trimedLine := bytes.Trim(line, "< ")
   106  			srv.out = make([]byte, len(trimedLine))
   107  			copy(srv.out, trimedLine)
   108  		case '/': // ignore lines starting with "/"
   109  		default:
   110  			srv.t.Fatalf("Invalid line %q", scan.Text())
   111  		}
   112  	}
   113  	if err := scan.Err(); err != nil {
   114  		srv.t.Fatalf("Failed to scan file: %v", err)
   115  	}
   116  }