github.com/filecoin-project/lassie@v0.23.0/pkg/internal/itest/e2e_test.go (about)

     1  //go:build !race
     2  
     3  package itest
     4  
     5  import (
     6  	"bytes"
     7  	"context"
     8  	"io"
     9  	"net/http"
    10  	"net/url"
    11  	"os"
    12  	"path/filepath"
    13  	"runtime"
    14  	"testing"
    15  	"time"
    16  
    17  	"github.com/ipfs/go-cid"
    18  	"github.com/ipld/go-car/v2"
    19  	trustlessutils "github.com/ipld/go-trustless-utils"
    20  	trustlesspathing "github.com/ipld/ipld/specs/pkg-go/trustless-pathing"
    21  	"github.com/ipni/storetheindex/test"
    22  	"github.com/stretchr/testify/require"
    23  )
    24  
    25  func TestTrustlessGatewayE2E(t *testing.T) {
    26  	switch os.Getenv("CI") {
    27  	case "":
    28  		// skip when not running in a CI environment
    29  		t.Skip("skipping when not in CI environment")
    30  	default:
    31  		if runtime.GOOS == "windows" {
    32  			// skip if windows, just too slow in CI, maybe revisit this later
    33  			t.Skip("skipping on windows in CI")
    34  		} // else in CI and we're good to go
    35  	}
    36  
    37  	req := require.New(t)
    38  
    39  	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
    40  	defer cancel()
    41  
    42  	tr := test.NewTestIpniRunner(t, ctx, t.TempDir())
    43  
    44  	t.Log("Running in test directory:", tr.Dir)
    45  
    46  	// install the lassie cmd, when done in tr.Run() will use the GOPATH/GOBIN
    47  	// in the test directory, so we get a localised `lassie` executable
    48  	lassie := filepath.Join(tr.Dir, "lassie")
    49  	tr.Run("go", "install", "../../../cmd/lassie/")
    50  
    51  	cwd, err := os.Getwd()
    52  	req.NoError(err)
    53  	err = os.Chdir(tr.Dir)
    54  	req.NoError(err)
    55  
    56  	// install the indexer to announce to
    57  	indexer := filepath.Join(tr.Dir, "storetheindex")
    58  	tr.Run("go", "install", "github.com/ipni/storetheindex@latest")
    59  	// install the ipni cli to inspect the indexer
    60  	ipni := filepath.Join(tr.Dir, "ipni")
    61  	tr.Run("go", "install", "github.com/ipni/ipni-cli/cmd/ipni@latest")
    62  	// install frisbii to serve the content
    63  	frisbii := filepath.Join(tr.Dir, "frisbii")
    64  	tr.Run("go", "install", "github.com/ipld/frisbii/cmd/frisbii@latest")
    65  
    66  	err = os.Chdir(cwd)
    67  	req.NoError(err)
    68  
    69  	// initialise and start the indexer and adjust the config
    70  	tr.Run(indexer, "init", "--store", "pebble", "--pubsub-topic", "/indexer/ingest/mainnet", "--no-bootstrap")
    71  	indexerReady := test.NewStdoutWatcher(test.IndexerReadyMatch)
    72  	cmdIndexer := tr.Start(test.NewExecution(indexer, "daemon").WithWatcher(indexerReady))
    73  	select {
    74  	case <-indexerReady.Signal:
    75  	case <-ctx.Done():
    76  		t.Fatal("timed out waiting for indexer to start")
    77  	}
    78  
    79  	testCases, root, err := trustlesspathing.Unixfs20mVarietyCases()
    80  	req.NoError(err)
    81  
    82  	carPath := trustlesspathing.Unixfs20mVarietyCARPath()
    83  
    84  	// start frisbii with the fixture CAR
    85  	frisbiiReady := test.NewStdoutWatcher("Announce() complete")
    86  	cmdFrisbii := tr.Start(test.NewExecution(frisbii,
    87  		"--listen", "localhost:37471",
    88  		"--announce", "roots",
    89  		"--announce-url", "http://localhost:3001/announce",
    90  		"--verbose",
    91  		"--car", carPath,
    92  	).WithWatcher(frisbiiReady))
    93  
    94  	select {
    95  	case <-frisbiiReady.Signal:
    96  	case <-ctx.Done():
    97  		t.Fatal("timed out waiting for frisbii to announce")
    98  	}
    99  
   100  	// wait for the CARs to be indexed
   101  	req.Eventually(func() bool {
   102  		mh := root.Hash().B58String()
   103  		findOutput := tr.Run(ipni, "find", "--no-priv", "-i", "http://localhost:3000", "-mh", mh)
   104  		t.Logf("import output:\n%s\n", findOutput)
   105  
   106  		if bytes.Contains(findOutput, []byte("not found")) {
   107  			return false
   108  		}
   109  		if !bytes.Contains(findOutput, []byte("Provider:")) {
   110  			t.Logf("mh %s: unexpected error: %s", mh, findOutput)
   111  			return false
   112  		}
   113  
   114  		t.Logf("mh %s: found", mh)
   115  		return true
   116  	}, 10*time.Second, time.Second)
   117  
   118  	expectedCarPath := root.String() + ".car"
   119  
   120  	t.Run("entire CAR fetch", func(t *testing.T) {
   121  		req := require.New(t)
   122  
   123  		// fetch the entire CAR
   124  		tr.Run(lassie,
   125  			"fetch",
   126  			"-vv",
   127  			"--ipni-endpoint", "http://localhost:3000",
   128  			root.String(),
   129  		)
   130  
   131  		_, err = os.Stat(expectedCarPath)
   132  		req.NoError(err)
   133  		_, expectedCids := carToCids(t, carPath)
   134  		gotRoot, gotCids := carToCids(t, expectedCarPath)
   135  		req.Equal(root, gotRoot)
   136  		req.ElementsMatch(expectedCids, gotCids)
   137  		req.NoError(os.Remove(expectedCarPath))
   138  	})
   139  
   140  	// start lassie daemon
   141  	lassieDaemonReady := test.NewStdoutWatcher("Lassie daemon listening on address")
   142  	cmdLassieDaemon := tr.Start(test.NewExecution(lassie, "daemon",
   143  		"-vv",
   144  		"--port", "30000",
   145  		"--ipni-endpoint", "http://localhost:3000",
   146  	).WithWatcher(lassieDaemonReady))
   147  
   148  	select {
   149  	case <-lassieDaemonReady.Signal:
   150  	case <-ctx.Done():
   151  		t.Fatal("timed out waiting for lassie daemon to start")
   152  	}
   153  
   154  	for _, testCase := range testCases {
   155  		t.Run(testCase.Name, func(t *testing.T) {
   156  			t.Run("lassie CLI fetch", func(t *testing.T) {
   157  				req := require.New(t)
   158  
   159  				// TODO: lassie should be able to handle http style queries on the commandline: testCase.AsQuery(),
   160  				args := []string{
   161  					"fetch",
   162  					"-vv",
   163  					"--ipni-endpoint", "http://localhost:3000",
   164  				}
   165  				if testCase.Scope != string(trustlessutils.DagScopeAll) {
   166  					args = append(args, "--dag-scope", testCase.Scope)
   167  				}
   168  				if testCase.ByteRange != "" {
   169  					args = append(args, "--entity-bytes", testCase.ByteRange)
   170  				}
   171  				args = append(args, testCase.Root.String())
   172  				if testCase.Path != "" {
   173  					args[len(args)-1] = args[len(args)-1] + "/" + testCase.Path
   174  				}
   175  				tr.Run(lassie, args...)
   176  
   177  				_, err = os.Stat(expectedCarPath)
   178  				req.NoError(err)
   179  				gotRoot, gotCids := carToCids(t, expectedCarPath)
   180  				req.Equal(testCase.Root, gotRoot)
   181  				req.ElementsMatch(testCase.ExpectedCids, gotCids)
   182  				req.NoError(os.Remove(expectedCarPath))
   183  			})
   184  
   185  			t.Run("lassie daemon fetch", func(t *testing.T) {
   186  				req := require.New(t)
   187  
   188  				reqUrl, err := url.Parse("http://localhost:30000/" + testCase.AsQuery())
   189  				req.NoError(err)
   190  
   191  				// download and read all body from URL along with Accept:application/vnd.ipld.car header
   192  				reqReq, err := http.NewRequestWithContext(ctx, http.MethodGet, reqUrl.String(), nil)
   193  				req.NoError(err)
   194  				reqReq.Header.Set("Accept", "application/vnd.ipld.car")
   195  				resp, err := http.DefaultClient.Do(reqReq)
   196  				req.NoError(err)
   197  				defer resp.Body.Close()
   198  
   199  				gotRoot, gotCids := carReaderToCids(t, resp.Body)
   200  				req.Equal(testCase.Root, gotRoot)
   201  				req.ElementsMatch(testCase.ExpectedCids, gotCids)
   202  			})
   203  		})
   204  	}
   205  
   206  	// stop and clean up
   207  	tr.Stop(cmdIndexer, time.Second)
   208  	tr.Stop(cmdFrisbii, time.Second)
   209  	tr.Stop(cmdLassieDaemon, time.Second)
   210  }
   211  
   212  func carToCids(t *testing.T, carPath string) (cid.Cid, []cid.Cid) {
   213  	req := require.New(t)
   214  
   215  	file, err := os.Open(carPath)
   216  	req.NoError(err)
   217  	defer file.Close()
   218  	return carReaderToCids(t, file)
   219  }
   220  
   221  func carReaderToCids(t *testing.T, r io.Reader) (cid.Cid, []cid.Cid) {
   222  	req := require.New(t)
   223  
   224  	cr, err := car.NewBlockReader(r)
   225  	req.NoError(err)
   226  	req.Len(cr.Roots, 1)
   227  
   228  	cids := make([]cid.Cid, 0)
   229  	for {
   230  		blk, err := cr.Next()
   231  		if err != nil {
   232  			req.ErrorIs(err, io.EOF)
   233  			break
   234  		}
   235  		cids = append(cids, blk.Cid())
   236  	}
   237  
   238  	return cr.Roots[0], cids
   239  }