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 }