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

     1  package lib
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"net/http/httptest"
     7  	"testing"
     8  
     9  	"github.com/qri-io/dataset"
    10  	"github.com/qri-io/ioes"
    11  	"github.com/qri-io/qri/auth/key"
    12  	"github.com/qri-io/qri/base/params"
    13  	"github.com/qri-io/qri/config"
    14  	"github.com/qri-io/qri/dsref"
    15  	dsrefspec "github.com/qri-io/qri/dsref/spec"
    16  	"github.com/qri-io/qri/registry"
    17  	"github.com/qri-io/qri/registry/regserver"
    18  	"github.com/qri-io/qri/remote"
    19  	repotest "github.com/qri-io/qri/repo/test"
    20  )
    21  
    22  func TestTwoActorRegistryIntegration(t *testing.T) {
    23  	tr := NewNetworkIntegrationTestRunner(t, "integration_two_actor_registry")
    24  	defer tr.Cleanup()
    25  
    26  	nasim := tr.InitNasim(t)
    27  
    28  	// - nasim creates a dataset
    29  	ref := InitWorldBankDataset(tr.Ctx, t, nasim)
    30  
    31  	// - nasim publishes to the registry
    32  	PushToRegistry(tr.Ctx, t, nasim, ref.Alias())
    33  
    34  	if err := AssertLogsEqual(nasim, tr.RegistryInst, ref); err != nil {
    35  		t.Error(err)
    36  	}
    37  
    38  	refs, err := tr.RegistryInst.Collection().ListRawRefs(tr.Ctx, &EmptyParams{})
    39  	if err != nil {
    40  		t.Fatal(err)
    41  	}
    42  	t.Log(refs)
    43  
    44  	hinshun := tr.InitHinshun(t)
    45  
    46  	// - hinshun searches the registry for nasim's dataset name, gets a result
    47  	if results := SearchFor(tr.Ctx, t, hinshun, "bank"); len(results) < 1 {
    48  		t.Logf("expected at least one result in registry search")
    49  	}
    50  
    51  	// - hunshun fetches a preview of nasim's dataset
    52  	// TODO (b5) - need to use the ref returned from search results
    53  	t.Log(ref.String())
    54  	Preview(tr.Ctx, t, hinshun, ref.String())
    55  
    56  	// - hinshun pulls nasim's dataset
    57  	Pull(tr.Ctx, t, hinshun, ref.Alias())
    58  
    59  	if err := AssertLogsEqual(nasim, hinshun, ref); err != nil {
    60  		t.Error(err)
    61  	}
    62  
    63  	// 5. nasim commits a new version
    64  	ref = Commit2WorldBank(tr.Ctx, t, nasim)
    65  
    66  	// 6. nasim re-publishes to the registry
    67  	PushToRegistry(tr.Ctx, t, nasim, ref.Alias())
    68  
    69  	// 7. hinshun logsyncs with the registry for world bank dataset, sees multiple versions
    70  	_, err = hinshun.WithSource("network").Dataset().Pull(tr.Ctx, &PullParams{LogsOnly: true, Ref: ref.String()})
    71  	if err != nil {
    72  		t.Errorf("cloning logs: %s", err)
    73  	}
    74  
    75  	if err := AssertLogsEqual(nasim, hinshun, ref); err != nil {
    76  		t.Error(err)
    77  	}
    78  
    79  	// TODO (b5) - assert hinshun DOES NOT have blocks for the latest commit to world bank dataset
    80  
    81  	// 8. hinshun pulls latest version
    82  	Pull(tr.Ctx, t, hinshun, ref.Alias())
    83  
    84  	// TODO (b5) - assert hinshun has world bank dataset blocks
    85  
    86  	// all three should now have the same HEAD reference & InitID
    87  	dsrefspec.ConsistentResolvers(t, dsref.Ref{
    88  		Username: ref.Username,
    89  		Name:     ref.Name,
    90  	},
    91  		nasim.Repo(),
    92  		hinshun.Repo(),
    93  		tr.RegistryInst.Repo(),
    94  	)
    95  }
    96  
    97  func TestReferencePulling(t *testing.T) {
    98  	tr := NewNetworkIntegrationTestRunner(t, "integration_reference_pulling")
    99  	defer tr.Cleanup()
   100  
   101  	nasim := tr.InitNasim(t)
   102  
   103  	// - nasim creates a dataset, publishes to registry
   104  	ref := InitWorldBankDataset(tr.Ctx, t, nasim)
   105  	PushToRegistry(tr.Ctx, t, nasim, ref.Alias())
   106  
   107  	// - nasim's local repo should reflect publication
   108  	logRes, err := nasim.Dataset().Activity(tr.Ctx, &ActivityParams{Ref: ref.Alias(), List: params.List{Limit: 1}})
   109  	if err != nil {
   110  		t.Fatal(err)
   111  	}
   112  
   113  	if logRes[0].Published != true {
   114  		t.Errorf("nasim has published HEAD. ref[0] published is false")
   115  	}
   116  
   117  	hinshun := tr.InitHinshun(t)
   118  
   119  	// fetch this from the registry by default
   120  	p := &GetParams{Ref: "nasim/world_bank_population"}
   121  	if _, err := hinshun.Dataset().Get(tr.Ctx, p); err != nil {
   122  		t.Fatal(err)
   123  	}
   124  
   125  	// re-run. dataset should now be local, and no longer require registry to
   126  	// resolve
   127  	if _, err = hinshun.WithSource("local").Dataset().Get(tr.Ctx, p); err != nil {
   128  		t.Fatal(err)
   129  	}
   130  
   131  	// create adnan
   132  	adnan := tr.InitAdnan(t)
   133  
   134  	// run a transform script that relies on world_bank_population, which adnan's
   135  	// node should automatically pull to execute this script
   136  	tfScriptData := `
   137  wbp = load_dataset("nasim/world_bank_population")
   138  ds = dataset.latest()
   139  
   140  ds.body = wbp.body + [["g","h","i",False,3]]
   141  dataset.commit(ds)
   142  `
   143  	scriptPath, err := tr.adnanRepo.WriteRootFile("transform.star", tfScriptData)
   144  	if err != nil {
   145  		t.Fatal(err)
   146  	}
   147  
   148  	saveParams := &SaveParams{
   149  		Ref: "me/wbp_plus_one",
   150  		FilePaths: []string{
   151  			scriptPath,
   152  		},
   153  		Apply: true,
   154  	}
   155  	_, err = adnan.Dataset().Save(tr.Ctx, saveParams)
   156  	if err != nil {
   157  		t.Fatal(err)
   158  	}
   159  
   160  	// - adnan's local repo should reflect nasim's publication
   161  	logRes, err = adnan.Dataset().Activity(tr.Ctx, &ActivityParams{Ref: ref.Alias(), List: params.List{Limit: 1}})
   162  	if err != nil {
   163  		t.Fatal(err)
   164  	}
   165  
   166  	if logRes[0].Published != true {
   167  		t.Errorf("adnan's log expects head was published, ref[0] published is false")
   168  	}
   169  }
   170  
   171  type NetworkIntegrationTestRunner struct {
   172  	Ctx        context.Context
   173  	prefix     string
   174  	TestCrypto key.CryptoGenerator
   175  
   176  	nasimRepo, hinshunRepo, adnanRepo *repotest.TempRepo
   177  	Nasim, Hinshun, Adnan             *Instance
   178  
   179  	registryRepo       *repotest.TempRepo
   180  	Registry           registry.Registry
   181  	RegistryInst       *Instance
   182  	RegistryHTTPServer *httptest.Server
   183  }
   184  
   185  func NewNetworkIntegrationTestRunner(t *testing.T, prefix string) *NetworkIntegrationTestRunner {
   186  	tr := &NetworkIntegrationTestRunner{
   187  		Ctx:        context.Background(),
   188  		prefix:     prefix,
   189  		TestCrypto: repotest.NewTestCrypto(),
   190  	}
   191  
   192  	tr.InitRegistry(t)
   193  
   194  	return tr
   195  }
   196  
   197  func (tr *NetworkIntegrationTestRunner) Cleanup() {
   198  	if tr.RegistryHTTPServer != nil {
   199  		tr.RegistryHTTPServer.Close()
   200  	}
   201  	if tr.registryRepo != nil {
   202  		tr.registryRepo.Delete()
   203  	}
   204  	if tr.nasimRepo != nil {
   205  		tr.nasimRepo.Delete()
   206  	}
   207  	if tr.hinshunRepo != nil {
   208  		tr.hinshunRepo.Delete()
   209  	}
   210  }
   211  
   212  func (tr *NetworkIntegrationTestRunner) InitNasim(t *testing.T) *Instance {
   213  	r, err := repotest.NewTempRepo("nasim", fmt.Sprintf("%s_nasim", tr.prefix), tr.TestCrypto)
   214  	if err != nil {
   215  		t.Fatal(err)
   216  	}
   217  
   218  	if tr.RegistryHTTPServer != nil {
   219  		cfg := r.GetConfig()
   220  		cfg.Registry.Location = tr.RegistryHTTPServer.URL
   221  		r.WriteConfigFile()
   222  	}
   223  	tr.nasimRepo = &r
   224  
   225  	if tr.Nasim, err = NewInstance(tr.Ctx, r.QriPath, OptIOStreams(ioes.NewDiscardIOStreams())); err != nil {
   226  		t.Fatal(err)
   227  	}
   228  
   229  	return tr.Nasim
   230  }
   231  
   232  func (tr *NetworkIntegrationTestRunner) InitHinshun(t *testing.T) *Instance {
   233  	r, err := repotest.NewTempRepo("hinshun", fmt.Sprintf("%s_hinshun", tr.prefix), tr.TestCrypto)
   234  	if err != nil {
   235  		t.Fatal(err)
   236  	}
   237  
   238  	if tr.RegistryHTTPServer != nil {
   239  		cfg := r.GetConfig()
   240  		cfg.Registry.Location = tr.RegistryHTTPServer.URL
   241  		r.WriteConfigFile()
   242  	}
   243  	tr.hinshunRepo = &r
   244  
   245  	if tr.Hinshun, err = NewInstance(tr.Ctx, tr.hinshunRepo.QriPath, OptIOStreams(ioes.NewDiscardIOStreams())); err != nil {
   246  		t.Fatal(err)
   247  	}
   248  
   249  	return tr.Hinshun
   250  }
   251  
   252  func (tr *NetworkIntegrationTestRunner) InitAdnan(t *testing.T) *Instance {
   253  	r, err := repotest.NewTempRepo("adnan", fmt.Sprintf("%s_adnan", tr.prefix), tr.TestCrypto)
   254  	if err != nil {
   255  		t.Fatal(err)
   256  	}
   257  
   258  	if tr.RegistryHTTPServer != nil {
   259  		cfg := r.GetConfig()
   260  		cfg.Registry.Location = tr.RegistryHTTPServer.URL
   261  		r.WriteConfigFile()
   262  	}
   263  	tr.adnanRepo = &r
   264  
   265  	if tr.Adnan, err = NewInstance(tr.Ctx, r.QriPath, OptIOStreams(ioes.NewDiscardIOStreams())); err != nil {
   266  		t.Fatal(err)
   267  	}
   268  
   269  	return tr.Adnan
   270  }
   271  
   272  func (tr *NetworkIntegrationTestRunner) InitRegistry(t *testing.T) {
   273  	rr, err := repotest.NewTempRepo("registry", fmt.Sprintf("%s_registry", tr.prefix), tr.TestCrypto)
   274  	if err != nil {
   275  		t.Fatal(err)
   276  	}
   277  	t.Logf("registry qri path: %s", rr.QriPath)
   278  
   279  	tr.registryRepo = &rr
   280  
   281  	cfg := rr.GetConfig()
   282  	cfg.Registry.Location = ""
   283  	cfg.RemoteServer = &config.RemoteServer{
   284  		Enabled:          true,
   285  		AcceptSizeMax:    -1,
   286  		AcceptTimeoutMs:  -1,
   287  		RequireAllBlocks: false,
   288  		AllowRemoves:     true,
   289  	}
   290  
   291  	rr.WriteConfigFile()
   292  
   293  	tr.RegistryInst, err = NewInstance(tr.Ctx, rr.QriPath, OptIOStreams(ioes.NewDiscardIOStreams()))
   294  	if err != nil {
   295  		t.Fatal(err)
   296  	}
   297  
   298  	node := tr.RegistryInst.Node()
   299  	if node == nil {
   300  		t.Fatal("creating a Registry for NetworkIntegration test fails if `qri connect` is running")
   301  	}
   302  
   303  	rem, err := remote.NewServer(node, cfg.RemoteServer, node.Repo.Logbook(), tr.RegistryInst.Bus())
   304  	if err != nil {
   305  		t.Fatal(err)
   306  	}
   307  
   308  	tr.Registry = registry.Registry{
   309  		Remote:   rem,
   310  		Profiles: registry.NewMemProfiles(),
   311  		Search:   regserver.MockRepoSearch{Repo: tr.RegistryInst.Repo()},
   312  	}
   313  
   314  	_, tr.RegistryHTTPServer = regserver.NewMockServerRegistry(tr.Registry)
   315  }
   316  
   317  func AssertLogsEqual(a, b *Instance, ref dsref.Ref) error {
   318  
   319  	aLogs, err := a.logbook.DatasetRef(context.Background(), ref)
   320  	if err != nil {
   321  		return fmt.Errorf("fetching logs for a instance: %s", err)
   322  	}
   323  
   324  	bLogs, err := b.logbook.DatasetRef(context.Background(), ref)
   325  	if err != nil {
   326  		return fmt.Errorf("fetching logs for b instance: %s", err)
   327  	}
   328  
   329  	if aLogs.ID() != bLogs.ID() {
   330  		return fmt.Errorf("log ID mismatch. %s != %s", aLogs.ID(), bLogs.ID())
   331  	}
   332  
   333  	if len(aLogs.Logs) != len(bLogs.Logs) {
   334  		return fmt.Errorf("oplength mismatch. %d != %d", len(aLogs.Logs), len(bLogs.Logs))
   335  	}
   336  
   337  	return nil
   338  }
   339  
   340  func InitWorldBankDataset(ctx context.Context, t *testing.T, inst *Instance) dsref.Ref {
   341  	res, err := inst.Dataset().Save(ctx, &SaveParams{
   342  		Ref: "me/world_bank_population",
   343  		Dataset: &dataset.Dataset{
   344  			Meta: &dataset.Meta{
   345  				Title: "World Bank Population",
   346  			},
   347  			BodyPath: "body.csv",
   348  			BodyBytes: []byte(`a,b,c,true,2
   349  d,e,f,false,3`),
   350  			Readme: &dataset.Readme{
   351  				ScriptPath: "readme.md",
   352  				Text:       "#World Bank Population\nhow many people live on this planet?",
   353  			},
   354  		},
   355  	})
   356  
   357  	if err != nil {
   358  		log.Fatalf("saving dataset version: %s", err)
   359  	}
   360  
   361  	return dsref.ConvertDatasetToVersionInfo(res).SimpleRef()
   362  }
   363  
   364  func Commit2WorldBank(ctx context.Context, t *testing.T, inst *Instance) dsref.Ref {
   365  	res, err := inst.Dataset().Save(ctx, &SaveParams{
   366  		Ref: "me/world_bank_population",
   367  		Dataset: &dataset.Dataset{
   368  			Meta: &dataset.Meta{
   369  				Title: "World Bank Population",
   370  			},
   371  			BodyPath: "body.csv",
   372  			BodyBytes: []byte(`a,b,c,true,2
   373  d,e,f,false,3
   374  g,g,i,true,4`),
   375  		},
   376  	})
   377  
   378  	if err != nil {
   379  		log.Fatalf("saving dataset version: %s", err)
   380  	}
   381  
   382  	return dsref.ConvertDatasetToVersionInfo(res).SimpleRef()
   383  }
   384  
   385  func PushToRegistry(ctx context.Context, t *testing.T, inst *Instance, refstr string) dsref.Ref {
   386  	res, err := inst.WithSource("local").Dataset().Push(ctx, &PushParams{
   387  		Ref: refstr,
   388  	})
   389  
   390  	if err != nil {
   391  		t.Fatalf("publishing dataset: %s", err)
   392  	}
   393  
   394  	return *res
   395  }
   396  
   397  func SearchFor(ctx context.Context, t *testing.T, inst *Instance, term string) []registry.SearchResult {
   398  	results, err := inst.Search().Search(ctx, &SearchParams{Query: term})
   399  	if err != nil {
   400  		t.Fatal(err)
   401  	}
   402  
   403  	return results
   404  }
   405  
   406  func Pull(ctx context.Context, t *testing.T, inst *Instance, refstr string) *dataset.Dataset {
   407  	t.Helper()
   408  	res, err := inst.WithSource("network").Dataset().Pull(ctx, &PullParams{Ref: refstr})
   409  	if err != nil {
   410  		t.Fatalf("cloning dataset %s: %s", refstr, err)
   411  	}
   412  	return res
   413  }
   414  
   415  func Preview(ctx context.Context, t *testing.T, inst *Instance, ref string) *dataset.Dataset {
   416  	t.Helper()
   417  	p := &PreviewParams{
   418  		Ref: ref,
   419  	}
   420  	res, err := inst.Remote().Preview(ctx, p)
   421  	if err != nil {
   422  		t.Fatal(err)
   423  	}
   424  	return res
   425  }