github.com/qri-io/qri@v0.10.1-0.20220104210721-c771715036cb/api/api_test.go (about)

     1  package api
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"errors"
     7  	"fmt"
     8  	"io"
     9  	"io/ioutil"
    10  	"mime/multipart"
    11  	"net/http"
    12  	"net/http/httptest"
    13  	"os"
    14  	"path/filepath"
    15  	"testing"
    16  	"time"
    17  
    18  	"github.com/beme/abide"
    19  	"github.com/gorilla/mux"
    20  	golog "github.com/ipfs/go-log"
    21  	ma "github.com/multiformats/go-multiaddr"
    22  	manet "github.com/multiformats/go-multiaddr/net"
    23  	apispec "github.com/qri-io/qri/api/spec"
    24  	"github.com/qri-io/qri/automation"
    25  	"github.com/qri-io/qri/base/dsfs"
    26  	"github.com/qri-io/qri/config"
    27  	testcfg "github.com/qri-io/qri/config/test"
    28  	"github.com/qri-io/qri/event"
    29  	"github.com/qri-io/qri/lib"
    30  	"github.com/qri-io/qri/logbook"
    31  	"github.com/qri-io/qri/p2p"
    32  	"github.com/qri-io/qri/repo"
    33  	"github.com/qri-io/qri/repo/test"
    34  )
    35  
    36  func init() {
    37  	abide.SnapshotsDir = "testdata"
    38  }
    39  
    40  func TestMain(m *testing.M) {
    41  	exit := m.Run()
    42  	abide.Cleanup()
    43  	os.Exit(exit)
    44  }
    45  
    46  func TestApiSpec(t *testing.T) {
    47  	if err := confirmQriNotRunning(); err != nil {
    48  		t.Fatal(err.Error())
    49  	}
    50  
    51  	tr := NewAPITestRunner(t)
    52  	defer tr.Delete()
    53  
    54  	ts := tr.MustTestServer(t)
    55  	defer ts.Close()
    56  
    57  	apispec.AssertHTTPAPISpec(t, ts.URL, "./spec")
    58  }
    59  
    60  func TestConnectNoP2P(t *testing.T) {
    61  	ctx, cancel := context.WithCancel(context.Background())
    62  	defer cancel()
    63  
    64  	node, teardown := newTestNode(t)
    65  	defer teardown()
    66  
    67  	inst := newTestInstanceWithProfileFromNode(ctx, node)
    68  	cfg := inst.GetConfig()
    69  	cfg.P2P.Enabled = false
    70  	if err := inst.ChangeConfig(cfg); err != nil {
    71  		t.Fatal(err)
    72  	}
    73  
    74  	s := New(inst)
    75  	ctx, cancel2 := context.WithTimeout(ctx, time.Millisecond*15)
    76  	defer cancel2()
    77  
    78  	if err := s.Serve(ctx); !errors.Is(err, http.ErrServerClosed) {
    79  		t.Fatal(err)
    80  	}
    81  }
    82  
    83  func newTestRepo(t *testing.T) (r repo.Repo, teardown func()) {
    84  	var err error
    85  	if err = confirmQriNotRunning(); err != nil {
    86  		t.Fatal(err.Error())
    87  	}
    88  
    89  	// bump up log level to keep test output clean
    90  	golog.SetLogLevel("qriapi", "error")
    91  
    92  	// to keep hashes consistent, artificially specify the timestamp by overriding
    93  	// the dsfs.Timestamp func
    94  	prevTs := dsfs.Timestamp
    95  	dsfs.Timestamp = func() time.Time { return time.Date(2001, 01, 01, 01, 01, 01, 01, time.UTC) }
    96  
    97  	logbookTsSec := 0
    98  	prevLogbookTs := logbook.NewTimestamp
    99  	logbook.NewTimestamp = func() int64 {
   100  		logbookTsSec++
   101  		return time.Date(2001, 01, 01, 01, 01, logbookTsSec, 01, time.UTC).Unix()
   102  	}
   103  
   104  	if r, err = test.NewTestRepo(); err != nil {
   105  		t.Fatalf("error allocating test repo: %s", err.Error())
   106  	}
   107  
   108  	teardown = func() {
   109  		golog.SetLogLevel("qriapi", "info")
   110  		// lib.SaveConfig = prevSaveConfig
   111  		dsfs.Timestamp = prevTs
   112  		logbook.NewTimestamp = prevLogbookTs
   113  	}
   114  
   115  	return
   116  }
   117  
   118  func newTestNode(t *testing.T) (node *p2p.QriNode, teardown func()) {
   119  	t.Helper()
   120  
   121  	var r repo.Repo
   122  	r, teardown = newTestRepo(t)
   123  	node, err := p2p.NewQriNode(r, testcfg.DefaultP2PForTesting(), event.NilBus, nil)
   124  	if err != nil {
   125  		t.Fatal(err.Error())
   126  	}
   127  	return node, teardown
   128  }
   129  
   130  func testConfigAndSetter() (cfg *config.Config, setCfg func(*config.Config) error) {
   131  	cfg = testcfg.DefaultConfigForTesting()
   132  	cfg.Profile = test.ProfileConfig()
   133  
   134  	setCfg = func(*config.Config) error { return nil }
   135  	return
   136  }
   137  
   138  func newTestInstanceWithProfileFromNode(ctx context.Context, node *p2p.QriNode) *lib.Instance {
   139  	return newTestInstanceWithProfileFromNodeAndOrchestratorOpts(ctx, node, nil)
   140  }
   141  
   142  func newTestInstanceWithProfileFromNodeAndOrchestratorOpts(ctx context.Context, node *p2p.QriNode, o *automation.OrchestratorOptions) *lib.Instance {
   143  	cfg := testcfg.DefaultConfigForTesting()
   144  	cfg.Profile, _ = node.Repo.Profiles().Owner(ctx).Encode()
   145  	return lib.NewInstanceFromConfigAndNodeAndBusAndOrchestratorOpts(ctx, cfg, node, event.NilBus, o)
   146  }
   147  
   148  type handlerTestCase struct {
   149  	method, endpoint string
   150  	body             []byte
   151  	muxVars          map[string]string
   152  }
   153  
   154  // runHandlerTestCases executes a slice of handlerTestCase against a handler
   155  func runHandlerTestCases(t *testing.T, name string, h http.HandlerFunc, cases []handlerTestCase, jsonHeader bool) {
   156  	for i, c := range cases {
   157  		name := fmt.Sprintf("%s %s case %d: %s %s", t.Name(), name, i, c.method, c.endpoint)
   158  		req := httptest.NewRequest(c.method, c.endpoint, bytes.NewBuffer(c.body))
   159  		if c.muxVars != nil {
   160  			req = mux.SetURLVars(req, c.muxVars)
   161  		}
   162  		setRefStringFromMuxVars(req)
   163  		if jsonHeader {
   164  			req.Header.Set("Content-Type", "application/json")
   165  		}
   166  		w := httptest.NewRecorder()
   167  
   168  		h(w, req)
   169  
   170  		res := w.Result()
   171  		abide.AssertHTTPResponse(t, name, res)
   172  	}
   173  }
   174  
   175  // runHandlerZipPostTestCases executes a slice of handlerTestCase against a handler using zip content-type
   176  func runHandlerZipPostTestCases(t *testing.T, name string, h http.HandlerFunc, cases []handlerTestCase) {
   177  	for i, c := range cases {
   178  		name := fmt.Sprintf("%s %s case %d: %s %s", t.Name(), name, i, c.method, c.endpoint)
   179  		req := httptest.NewRequest(c.method, c.endpoint, bytes.NewBuffer(c.body))
   180  		req.Header.Set("Content-Type", "application/zip")
   181  		w := httptest.NewRecorder()
   182  
   183  		h(w, req)
   184  
   185  		res := w.Result()
   186  		abide.AssertHTTPResponse(t, name, res)
   187  	}
   188  }
   189  
   190  // mustFile reads file bytes, calling t.Fatalf if the file doesn't exist
   191  func mustFile(t *testing.T, filename string) []byte {
   192  	data, err := ioutil.ReadFile(filename)
   193  	if err != nil {
   194  		t.Fatalf("error opening test file: %s: %s", filename, err.Error())
   195  	}
   196  	return data
   197  }
   198  
   199  func confirmQriNotRunning() error {
   200  	addr, err := ma.NewMultiaddr(config.DefaultAPIAddress)
   201  	if err != nil {
   202  		return fmt.Errorf(err.Error())
   203  	}
   204  	l, err := manet.Listen(addr)
   205  	if err != nil {
   206  		return fmt.Errorf("it looks like a qri server is already running on address %s, please close before running tests", config.DefaultAPIAddress)
   207  	}
   208  
   209  	l.Close()
   210  	return nil
   211  }
   212  
   213  func TestHealthCheck(t *testing.T) {
   214  	prevAPIVer := APIVersion
   215  	APIVersion = "test_version"
   216  	defer func() {
   217  		APIVersion = prevAPIVer
   218  	}()
   219  
   220  	healthCheckCases := []handlerTestCase{
   221  		{"GET", "/", nil, nil},
   222  	}
   223  	runHandlerTestCases(t, "health check", HealthCheckHandler, healthCheckCases, true)
   224  }
   225  
   226  type handlerMimeMultipartTestCase struct {
   227  	method    string
   228  	endpoint  string
   229  	filePaths map[string]string
   230  	params    map[string]string
   231  	muxVars   map[string]string
   232  }
   233  
   234  func runMimeMultipartHandlerTestCases(t *testing.T, name string, h http.HandlerFunc, cases []handlerMimeMultipartTestCase) {
   235  	for i, c := range cases {
   236  		body := &bytes.Buffer{}
   237  		writer := multipart.NewWriter(body)
   238  		name := fmt.Sprintf("%s %s case %d: %s %s", t.Name(), name, i, c.method, c.endpoint)
   239  
   240  		for name, path := range c.filePaths {
   241  			data, err := os.Open(path)
   242  			if err != nil {
   243  				t.Fatalf("error opening datafile: %s %s", name, err)
   244  			}
   245  			dataPart, err := writer.CreateFormFile(name, filepath.Base(path))
   246  			if err != nil {
   247  				t.Fatalf("error adding data file to form: %s %s", name, err)
   248  			}
   249  
   250  			if _, err := io.Copy(dataPart, data); err != nil {
   251  				t.Fatalf("error copying data: %s %s %s", c.method, c.endpoint, err)
   252  			}
   253  		}
   254  		for key, val := range c.params {
   255  			if err := writer.WriteField(key, val); err != nil {
   256  				t.Fatalf("error adding field to writer: %s %s", name, err)
   257  			}
   258  		}
   259  
   260  		if err := writer.Close(); err != nil {
   261  			t.Fatalf("error closing writer: %s", err)
   262  		}
   263  
   264  		req := httptest.NewRequest(c.method, c.endpoint, body)
   265  		req.Header.Add("Content-Type", writer.FormDataContentType())
   266  		if c.muxVars != nil {
   267  			req = mux.SetURLVars(req, c.muxVars)
   268  		}
   269  		setRefStringFromMuxVars(req)
   270  
   271  		w := httptest.NewRecorder()
   272  
   273  		h(w, req)
   274  
   275  		res := w.Result()
   276  		abide.AssertHTTPResponse(t, name, res)
   277  	}
   278  }
   279  
   280  // NewFilesRequest creates a mime/multipart http.Request with files specified by a map of param : filepath,
   281  // and form values specified by a map, params
   282  func NewFilesRequest(method, endpoint, url string, filePaths, params map[string]string) (*http.Request, error) {
   283  	body := &bytes.Buffer{}
   284  	writer := multipart.NewWriter(body)
   285  
   286  	for name, path := range filePaths {
   287  		data, err := os.Open(path)
   288  		if err != nil {
   289  			return nil, fmt.Errorf("error opening datafile: %s %s %s", method, endpoint, err)
   290  		}
   291  		dataPart, err := writer.CreateFormFile(name, filepath.Base(path))
   292  		if err != nil {
   293  			return nil, fmt.Errorf("error adding data file to form: %s %s %s", method, endpoint, err)
   294  		}
   295  
   296  		if _, err := io.Copy(dataPart, data); err != nil {
   297  			return nil, fmt.Errorf("error copying data: %s %s %s", method, endpoint, err)
   298  		}
   299  	}
   300  	for key, val := range params {
   301  		if err := writer.WriteField(key, val); err != nil {
   302  			return nil, fmt.Errorf("error adding field to writer: %s %s %s", method, endpoint, err)
   303  		}
   304  	}
   305  
   306  	if err := writer.Close(); err != nil {
   307  		return nil, fmt.Errorf("error closing writer: %s", err)
   308  	}
   309  
   310  	req, err := http.NewRequest(method, url, body)
   311  	if err != nil {
   312  		return nil, fmt.Errorf("error creating request: %s %s %s", method, endpoint, err)
   313  	}
   314  
   315  	req.Header.Add("Content-Type", writer.FormDataContentType())
   316  
   317  	return req, nil
   318  }