diff --git a/cmd/fetch_test.go b/cmd/fetch_test.go index 6584b5516..201c782ba 100644 --- a/cmd/fetch_test.go +++ b/cmd/fetch_test.go @@ -2,77 +2,157 @@ package cmd import ( "context" + "fmt" + "net/http" + "regexp" "testing" "time" + + "github.com/google/go-cmp/cmp" + "github.com/qri-io/qri/api" + "github.com/qri-io/qri/config" + "github.com/qri-io/qri/lib" + "github.com/qri-io/qri/logbook" ) func TestFetchCommand(t *testing.T) { - r := NewTestRunner(t, "peer_a", "qri_test_fetch_a") - defer r.Delete() + a := NewTestRunner(t, "peer_a", "qri_test_fetch_a") + defer a.Delete() - ctx, done := context.WithCancel(context.Background()) - defer done() + // TODO(dustmop): Move most of the below hooks into a common testRunner. Maybe the basic + // TestRunner will work? - cmdR := r.CreateCommandRunner(ctx) - err := executeCommand(cmdR, "qri save --body=testdata/movies/body_ten.csv me/test_movies") + // Set the location to New York so that timezone printing is consistent + location, err := time.LoadLocation("America/New_York") if err != nil { - t.Fatal(err.Error()) + panic(err) } + locOrig := StringerLocation + StringerLocation = location - cmdR = r.CreateCommandRunner(ctx) - if err = executeCommand(cmdR, "qri save --body=testdata/movies/body_thirty.csv me/test_movies"); err != nil { - t.Fatal(err) + // Restore the location function + a.Teardown = func() { + StringerLocation = locOrig } - cmdR = r.CreateCommandRunner(ctx) - if err = executeCommand(cmdR, "qri log peer_a/test_movies"); err != nil { - t.Fatal(err) + // Hook timestamp generation. + prevTimestampFunc := logbook.NewTimestamp + logbook.NewTimestamp = func() int64 { + return 1000 } + defer func() { + logbook.NewTimestamp = prevTimestampFunc + }() + + // Save a version with some rows in its body + a.MustExec(t, "qri save --body=testdata/movies/body_ten.csv me/test_movies") + + // Save another version with more rows + a.MustExec(t, "qri save --body=testdata/movies/body_thirty.csv me/test_movies") - text := r.GetCommandOutput() - // TODO (b5) - make this acutally inspect once we have stable timestamps in logs - if len(text) == 0 { - t.Errorf("expected log to produce a non-zero length output.") + // Get the log, should have two versions. + actual := a.MustExec(t, "qri log peer_a/test_movies") + expect := `1 Commit: /ipfs/QmbjY9YG6xKfrPxiXA9eBkJSZiiRRtfKoaS9LSnyVvCAuA + Date: Sun Dec 31 20:02:01 EST 2000 + Storage: local + Size: 720 B + + structure updated 3 fields + structure: + updated checksum + updated entries + updated length + +2 Commit: /ipfs/QmXfgnK7XmyZcRfKrhDysRh5AcHqQntLy98i4joDqopqx6 + Date: Sun Dec 31 20:01:01 EST 2000 + Storage: local + Size: 224 B + + created dataset + +` + if diff := cmp.Diff(expect, actual); diff != "" { + t.Errorf("result mismatch (-want +got):%s\n", diff) } - cmdR = r.CreateCommandRunner(ctx) - if err = executeCommand(cmdR, "qri config set remote.enabled true rpc.enabled false"); err != nil { + // Enable remote and RPC in the config + a.MustExec(t, "qri config set remote.enabled true rpc.enabled false") + + ctx := context.Background() + + // Create a remote that makes these versions available + remoteInst, err := lib.NewInstance( + ctx, + a.RepoRoot.QriPath, + lib.OptStdIOStreams(), + lib.OptSetIPFSPath(a.RepoRoot.IPFSPath), + ) + if err != nil { + t.Fatal(err) + } + if err := remoteInst.Connect(ctx); err != nil { t.Fatal(err) } - cmdR = r.CreateCommandRunner(ctx) - go func() { - if err = executeCommand(cmdR, "qri connect"); err != nil { - t.Fatal(err) - } - }() - - // TODO (b5) - this is horrible. we should block on a channel receive for connectedness - time.Sleep(time.Second * 5) + // Made an HTTP server for our remote + remoteServer := api.New(remoteInst) + httpServer := &http.Server{} + httpServer.Handler = api.NewServerRoutes(remoteServer) + + // Serve on an available port + // TODO(dustmop): This port could actually be randomized to make this more robust + const RemotePort = 9876 + apiConfig := config.API{ + Enabled: true, + Port: RemotePort, + RemoteMode: true, + } + go api.StartServer(&apiConfig, httpServer) + defer httpServer.Close() + // Construct a second peer B. b := NewTestRunner(t, "peer_b", "qri_test_fetch_b") defer b.Delete() - cmdBr := b.CreateCommandRunner(ctx) - if err = executeCommand(cmdBr, "qri log peer_b/test_movies"); err == nil { + // Expect an error when trying to list an unavailable dataset + err = b.ExecCommand("qri log peer_b/test_movies") + expectErr := `repo: not found` + if err == nil { t.Fatal("expected fetch on non-existant log to error") } - - cmdBr = b.CreateCommandRunner(ctx) - if err = executeCommand(cmdBr, "qri config set remotes.a_node http://localhost:2503"); err != nil { - t.Fatal(err) + if expectErr != err.Error() { + t.Errorf("error mismatch, expect: %s, got: %s", expectErr, err) } - cmdBr = b.CreateCommandRunner(ctx) - if err = executeCommand(cmdBr, "qri fetch peer_a/test_movies --remote a_node"); err != nil { - t.Fatal(err) + // Assign peer A as a remote for peer B + cfgCmdText := fmt.Sprintf("qri config set remotes.a_node http://localhost:%d", RemotePort) + b.MustExec(t, cfgCmdText) + + // Have peer B fetch from peer A, output correlates to the log from peer A earlier + actual = b.MustExec(t, "qri fetch peer_a/test_movies --remote a_node") + expect = `1 peer_a/test_movies + /ipfs/QmbjY9YG6xKfrPxiXA9eBkJSZiiRRtfKoaS9LSnyVvCAuA + foreign + 720 B, 0 entries, 0 errors + +2 peer_a/test_movies + /ipfs/QmXfgnK7XmyZcRfKrhDysRh5AcHqQntLy98i4joDqopqx6 + foreign + 224 B, 0 entries, 0 errors + +` + if diff := cmp.Diff(expect, actual); diff != "" { + t.Errorf("result mismatch (-want +got):%s\n", diff) } - cmdBr = b.CreateCommandRunner(ctx) - if err = executeCommand(cmdBr, "qri logbook --raw"); err != nil { - t.Fatal(err) - } + // Regex that replaces the timestamp with just static text + fixTs := regexp.MustCompile(`"timestamp":"[0-9TZ.:-]*"`) - text = b.GetCommandOutput() - t.Logf("%s", text) + // Verify the logbook on peer B doesn't contain the fetched info + output := b.MustExec(t, "qri logbook --raw") + actual = string(fixTs.ReplaceAll([]byte(output), []byte(`"timestamp":timeStampHere`))) + expect = `[{"ops":[{"type":"init","model":"user","name":"peer_b","authorID":"QmeL2mdVka1eahKENjehK6tBxkkpk5dNQ1qMcgWi7Hrb4B","timestamp":timeStampHere}]}]` + if diff := cmp.Diff(expect, actual); diff != "" { + t.Errorf("result mismatch (-want +got):%s\n", diff) + } } diff --git a/remote/remote.go b/remote/remote.go index a7bc20b5c..2c0c54dfa 100644 --- a/remote/remote.go +++ b/remote/remote.go @@ -109,6 +109,10 @@ func NewRemote(node *p2p.QriNode, cfg *config.Remote, opts ...func(o *Options)) opt(o) } + if node == nil { + return nil, fmt.Errorf("remote requires a non-nil node") + } + r := &Remote{ node: node,