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

     1  package cmd
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"net/http"
     8  	"net/http/httptest"
     9  	"regexp"
    10  	"testing"
    11  	"time"
    12  
    13  	"github.com/google/go-cmp/cmp"
    14  	"github.com/qri-io/qri/api"
    15  	"github.com/qri-io/qri/config"
    16  	"github.com/qri-io/qri/lib"
    17  )
    18  
    19  func TestFetchCommand(t *testing.T) {
    20  	t.Skip(`This currently won't work b/c commands can't be executed while the "temp registry" is running.
    21  	New rules state there can only be one instance at a time, and in this case an
    22  	instance exists to back the testRunner. requests to execute commands below don't
    23  	work b/c the instance contends for the repo lock. At least I (b5) think that's
    24  	what's going on :/`)
    25  
    26  	a := NewTestRunner(t, "peer_a", "qri_test_fetch_a")
    27  	defer a.Delete()
    28  
    29  	// Save a version with some rows in its body
    30  	a.MustExec(t, "qri save --body=testdata/movies/body_ten.csv me/test_movies")
    31  
    32  	// Save another version with more rows
    33  	a.MustExec(t, "qri save --body=testdata/movies/body_thirty.csv me/test_movies")
    34  
    35  	// Get the log, should have two versions.
    36  	actual := a.MustExec(t, "qri log peer_a/test_movies")
    37  	expect := `1   Commit:  /ipfs/QmXmnH1tFKyG493wsFiisZ14N4cjrymZZ6pqK3Vr9vWS2p
    38      Date:    Sun Dec 31 20:02:01 EST 2000
    39      Storage: local
    40      Size:    720 B
    41  
    42      body changed by 70%
    43      body:
    44      	changed by 70%
    45  
    46  2   Commit:  /ipfs/QmNX9ZKXtdskpYSQ5spd1qvqB2CPoWfJbdAcWoFndintrF
    47      Date:    Sun Dec 31 20:01:01 EST 2000
    48      Storage: local
    49      Size:    224 B
    50  
    51      created dataset from body_ten.csv
    52  
    53  `
    54  	if diff := cmp.Diff(expect, actual); diff != "" {
    55  		t.Errorf("result mismatch (-want +got):%s\n", diff)
    56  	}
    57  
    58  	// Enable remote and RPC in the config
    59  	a.MustExec(t, "qri config set remote.enabled true rpc.enabled false")
    60  
    61  	ctx, cancel := context.WithCancel(context.Background())
    62  	defer cancel()
    63  
    64  	// Create a remote that makes these versions available
    65  	remoteInst, err := lib.NewInstance(
    66  		ctx,
    67  		a.RepoRoot.RootPath,
    68  		lib.OptStdIOStreams(),
    69  		lib.OptSetIPFSPath(a.RepoRoot.IPFSPath),
    70  	)
    71  	if err != nil {
    72  		t.Fatal(err)
    73  	}
    74  	if err := remoteInst.ConnectP2P(ctx); err != nil {
    75  		t.Fatal(err)
    76  	}
    77  
    78  	// Made an HTTP server for our remote
    79  	remoteServer := api.New(remoteInst)
    80  	httpServer := &http.Server{}
    81  	httpServer.Handler = api.NewServerRoutes(remoteServer)
    82  
    83  	// Serve on an available port
    84  	// TODO(dustmop): This port could actually be randomized to make this more robust
    85  	const RemotePort = "9876"
    86  	apiConfig := config.API{
    87  		Enabled: true,
    88  		Address: fmt.Sprintf("/ip4/0.0.0.0/tcp/%s", RemotePort),
    89  	}
    90  	go api.StartServer(&apiConfig, httpServer)
    91  	defer httpServer.Close()
    92  
    93  	// Construct a second peer B.
    94  	b := NewTestRunnerWithTempRegistry(t, "peer_b", "qri_test_fetch_b")
    95  	defer b.Delete()
    96  
    97  	// Expect an error when trying to list an unavailable dataset
    98  	err = b.ExecCommand("qri log peer_b/test_movies")
    99  	expectErr := `reference not found`
   100  	if err == nil {
   101  		t.Fatal("expected fetch on non-existent log to error")
   102  	}
   103  	if expectErr != err.Error() {
   104  		t.Errorf("error mismatch, expect: %s, got: %s", expectErr, err)
   105  	}
   106  
   107  	RemoteHost := fmt.Sprintf("http://localhost:%s", RemotePort)
   108  
   109  	// Assign peer A as a remote for peer B
   110  	cfgCmdText := fmt.Sprintf("qri config set remotes.a_node %s", RemoteHost)
   111  	b.MustExec(t, cfgCmdText)
   112  
   113  	// Have peer B fetch from peer A, output correlates to the log from peer A earlier
   114  	actual = b.MustExec(t, "qri log peer_a/test_movies --remote a_node")
   115  	expect = `1   Commit:  /ipfs/QmXmnH1tFKyG493wsFiisZ14N4cjrymZZ6pqK3Vr9vWS2p
   116      Date:    Sun Dec 31 20:02:01 EST 2000
   117      Storage: remote
   118      Size:    720 B
   119  
   120      body changed by 70%
   121  
   122  2   Commit:  /ipfs/QmNX9ZKXtdskpYSQ5spd1qvqB2CPoWfJbdAcWoFndintrF
   123      Date:    Sun Dec 31 20:01:01 EST 2000
   124      Storage: remote
   125      Size:    224 B
   126  
   127      created dataset from body_ten.csv
   128  
   129  `
   130  	if diff := cmp.Diff(expect, actual); diff != "" {
   131  		t.Errorf("result mismatch (-want +got):%s\n", diff)
   132  	}
   133  
   134  	// Regex that replaces the timestamp with just static text
   135  	fixTs := regexp.MustCompile(`"(timestamp|commitTime)":\s?"[0-9TZ.:+-]*?"`)
   136  
   137  	// Verify the logbook on peer B doesn't contain the fetched info
   138  	output := b.MustExec(t, "qri logbook --raw")
   139  	actual = string(fixTs.ReplaceAll([]byte(output), []byte(`"timestamp":"timeStampHere"`)))
   140  	expect = `[{"ops":[{"type":"init","model":"user","name":"peer_b","authorID":"QmeL2mdVka1eahKENjehK6tBxkkpk5dNQ1qMcgWi7Hrb4B","timestamp":"timeStampHere"}]}]`
   141  	if diff := cmp.Diff(expect, actual); diff != "" {
   142  		t.Errorf("result mismatch (-want +got):%s\n", diff)
   143  	}
   144  
   145  	// TODO(dustmop): Try to add the below to a separate test in api/
   146  
   147  	localInst, err := lib.NewInstance(
   148  		ctx,
   149  		b.RepoRoot.RootPath,
   150  		lib.OptStdIOStreams(),
   151  		lib.OptSetIPFSPath(b.RepoRoot.IPFSPath),
   152  	)
   153  	localLogHandler := lib.NewHTTPRequestHandler(localInst, "log.history")
   154  
   155  	//
   156  	// Validate the outputs of history and fetch
   157  	//
   158  
   159  	remoteLogHandler := lib.NewHTTPRequestHandler(remoteServer.Instance, "log.history")
   160  
   161  	// Validates output of history for a remote dataset getting history for a
   162  	// dataset in its own namespace in its own repo
   163  	actualStatusCode, actualBody := APICall(
   164  		"POST",
   165  		"/history",
   166  		map[string]string{"refstr": "peer_a/test_movies"},
   167  		remoteLogHandler)
   168  	if actualStatusCode != 200 {
   169  		t.Errorf("expected status code 200, got %d", actualStatusCode)
   170  	}
   171  	actualBody = string(fixTs.ReplaceAll([]byte(actualBody), []byte(`"commitTime":"timeStampHere"`)))
   172  	expectBody := `{"data":[{"username":"peer_a","name":"test_movies","path":"/ipfs/QmXmnH1tFKyG493wsFiisZ14N4cjrymZZ6pqK3Vr9vWS2p","bodySize":720,"commitTime":"timeStampHere","commitTitle":"body changed by 70%","commitMessage":"body:\n\tchanged by 70%"},{"username":"peer_a","name":"test_movies","path":"/ipfs/QmNX9ZKXtdskpYSQ5spd1qvqB2CPoWfJbdAcWoFndintrF","bodySize":224,"commitTime":"timeStampHere","commitTitle":"created dataset from body_ten.csv","commitMessage":"created dataset from body_ten.csv"}],"meta":{"code":200},"pagination":{"page":1,"pageSize":100,"nextUrl":"/history/peer_a/test_movies?page=2\u0026pageSize=100","prevUrl":""}}`
   173  	if diff := cmp.Diff(expectBody, actualBody); diff != "" {
   174  		t.Errorf("body mismatch (-want +got):%s\n", diff)
   175  	}
   176  
   177  	// Validates output of fetching from a remote for a remote dataset
   178  	actualStatusCode, actualBody = APICall(
   179  		"POST",
   180  		"/history",
   181  		map[string]string{
   182  			"refstr": "peer_a/test_movies",
   183  			"remote": "a_node",
   184  		},
   185  		localLogHandler)
   186  	if actualStatusCode != 200 {
   187  		t.Errorf("expected status code 200, got %d", actualStatusCode)
   188  	}
   189  	actualBody = string(fixTs.ReplaceAll([]byte(actualBody), []byte(`"commitTime":"timeStampHere"`)))
   190  	expectBody = `{"data":[{"username":"peer_a","name":"test_movies","path":"/ipfs/QmXmnH1tFKyG493wsFiisZ14N4cjrymZZ6pqK3Vr9vWS2p","foreign":true,"bodySize":720,"commitTime":"timeStampHere","commitTitle":"body changed by 70%"},{"username":"peer_a","name":"test_movies","path":"/ipfs/QmNX9ZKXtdskpYSQ5spd1qvqB2CPoWfJbdAcWoFndintrF","foreign":true,"bodySize":224,"commitTime":"timeStampHere","commitTitle":"created dataset from body_ten.csv"}],"meta":{"code":200},"pagination":{"page":1,"pageSize":100,"nextUrl":"/history/peer_a/test_movies?page=2\u0026pageSize=100\u0026remote=a_node","prevUrl":""}}`
   191  	if diff := cmp.Diff(expectBody, actualBody); diff != "" {
   192  		t.Errorf("body mismatch (-want +got):%s\n", diff)
   193  	}
   194  }
   195  
   196  // APICall calls the api and returns the status code and body
   197  func APICall(method, reqURL string, params map[string]string, hf http.HandlerFunc) (int, string) {
   198  	req := httptest.NewRequest(method, reqURL, nil)
   199  	ctx, cncl := context.WithTimeout(req.Context(), time.Second*10)
   200  	req.WithContext(ctx)
   201  	defer cncl()
   202  
   203  	// add parameters from map
   204  	if params != nil {
   205  		q := req.URL.Query()
   206  		for key := range params {
   207  			q.Add(key, params[key])
   208  		}
   209  		req.URL.RawQuery = q.Encode()
   210  	}
   211  
   212  	// Set form-encoded header so server will find the parameters
   213  	req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
   214  	w := httptest.NewRecorder()
   215  	hf(w, req)
   216  	res := w.Result()
   217  	statusCode := res.StatusCode
   218  	bodyBytes, err := ioutil.ReadAll(res.Body)
   219  	if err != nil {
   220  		panic(err)
   221  	}
   222  	return statusCode, string(bodyBytes)
   223  }