github.com/qri-io/qri@v0.10.1-0.20220104210721-c771715036cb/repo/test/test_repo.go (about)

     1  package test
     2  
     3  import (
     4  	"context"
     5  	"crypto/rand"
     6  	"encoding/base64"
     7  	"fmt"
     8  	"path/filepath"
     9  	"runtime"
    10  
    11  	crypto "github.com/libp2p/go-libp2p-core/crypto"
    12  	"github.com/qri-io/dataset"
    13  	"github.com/qri-io/dataset/dstest"
    14  	"github.com/qri-io/qfs"
    15  	"github.com/qri-io/qfs/muxfs"
    16  	"github.com/qri-io/qri/base/dsfs"
    17  	"github.com/qri-io/qri/config"
    18  	"github.com/qri-io/qri/dsref"
    19  	"github.com/qri-io/qri/event"
    20  	"github.com/qri-io/qri/logbook"
    21  	"github.com/qri-io/qri/profile"
    22  	"github.com/qri-io/qri/repo"
    23  	reporef "github.com/qri-io/qri/repo/ref"
    24  )
    25  
    26  // base64-encoded Test Private Key, decoded in init
    27  // peerId: QmZePf5LeXow3RW5U1AgEiNbW46YnRGhZ7HPvm1UmPFPwt
    28  var (
    29  	_testPk   = []byte(`CAASpgkwggSiAgEAAoIBAQC/7Q7fILQ8hc9g07a4HAiDKE4FahzL2eO8OlB1K99Ad4L1zc2dCg+gDVuGwdbOC29IngMA7O3UXijycckOSChgFyW3PafXoBF8Zg9MRBDIBo0lXRhW4TrVytm4Etzp4pQMyTeRYyWR8e2hGXeHArXM1R/A/SjzZUbjJYHhgvEE4OZy7WpcYcW6K3qqBGOU5GDMPuCcJWac2NgXzw6JeNsZuTimfVCJHupqG/dLPMnBOypR22dO7yJIaQ3d0PFLxiDG84X9YupF914RzJlopfdcuipI+6gFAgBw3vi6gbECEzcohjKf/4nqBOEvCDD6SXfl5F/MxoHurbGBYB2CJp+FAgMBAAECggEAaVOxe6Y5A5XzrxHBDtzjlwcBels3nm/fWScvjH4dMQXlavwcwPgKhy2NczDhr4X69oEw6Msd4hQiqJrlWd8juUg6vIsrl1wS/JAOCS65fuyJfV3Pw64rWbTPMwO3FOvxj+rFghZFQgjg/i45uHA2UUkM+h504M5Nzs6Arr/rgV7uPGR5e5OBw3lfiS9ZaA7QZiOq7sMy1L0qD49YO1ojqWu3b7UaMaBQx1Dty7b5IVOSYG+Y3U/dLjhTj4Hg1VtCHWRm3nMOE9cVpMJRhRzKhkq6gnZmni8obz2BBDF02X34oQLcHC/Wn8F3E8RiBjZDI66g+iZeCCUXvYz0vxWAQQKBgQDEJu6flyHPvyBPAC4EOxZAw0zh6SF/r8VgjbKO3n/8d+kZJeVmYnbsLodIEEyXQnr35o2CLqhCvR2kstsRSfRz79nMIt6aPWuwYkXNHQGE8rnCxxyJmxV4S63GczLk7SIn4KmqPlCI08AU0TXJS3zwh7O6e6kBljjPt1mnMgvr3QKBgQD6fAkdI0FRZSXwzygx4uSg47Co6X6ESZ9FDf6ph63lvSK5/eue/ugX6p/olMYq5CHXbLpgM4EJYdRfrH6pwqtBwUJhlh1xI6C48nonnw+oh8YPlFCDLxNG4tq6JVo071qH6CFXCIank3ThZeW5a3ZSe5pBZ8h4bUZ9H8pJL4C7yQKBgFb8SN/+/qCJSoOeOcnohhLMSSD56MAeK7KIxAF1jF5isr1TP+rqiYBtldKQX9bIRY3/8QslM7r88NNj+aAuIrjzSausXvkZedMrkXbHgS/7EAPflrkzTA8fyH10AsLgoj/68mKr5bz34nuY13hgAJUOKNbvFeC9RI5g6eIqYH0FAoGAVqFTXZp12rrK1nAvDKHWRLa6wJCQyxvTU8S1UNi2EgDJ492oAgNTLgJdb8kUiH0CH0lhZCgr9py5IKW94OSM6l72oF2UrS6PRafHC7D9b2IV5Al9lwFO/3MyBrMocapeeyaTcVBnkclz4Qim3OwHrhtFjF1ifhP9DwVRpuIg+dECgYANwlHxLe//tr6BM31PUUrOxP5Y/cj+ydxqM/z6papZFkK6Mvi/vMQQNQkh95GH9zqyC5Z/yLxur4ry1eNYty/9FnuZRAkEmlUSZ/DobhU0Pmj8Hep6JsTuMutref6vCk2n02jc9qYmJuD7iXkdXDSawbEG6f5C4MUkJ38z1t1OjA==`)
    30  	testPk    = []byte(`CAASpgkwggSiAgEAAoIBAQC/7Q7fILQ8hc9g07a4HAiDKE4FahzL2eO8OlB1K99Ad4L1zc2dCg+gDVuGwdbOC29IngMA7O3UXijycckOSChgFyW3PafXoBF8Zg9MRBDIBo0lXRhW4TrVytm4Etzp4pQMyTeRYyWR8e2hGXeHArXM1R/A/SjzZUbjJYHhgvEE4OZy7WpcYcW6K3qqBGOU5GDMPuCcJWac2NgXzw6JeNsZuTimfVCJHupqG/dLPMnBOypR22dO7yJIaQ3d0PFLxiDG84X9YupF914RzJlopfdcuipI+6gFAgBw3vi6gbECEzcohjKf/4nqBOEvCDD6SXfl5F/MxoHurbGBYB2CJp+FAgMBAAECggEAaVOxe6Y5A5XzrxHBDtzjlwcBels3nm/fWScvjH4dMQXlavwcwPgKhy2NczDhr4X69oEw6Msd4hQiqJrlWd8juUg6vIsrl1wS/JAOCS65fuyJfV3Pw64rWbTPMwO3FOvxj+rFghZFQgjg/i45uHA2UUkM+h504M5Nzs6Arr/rgV7uPGR5e5OBw3lfiS9ZaA7QZiOq7sMy1L0qD49YO1ojqWu3b7UaMaBQx1Dty7b5IVOSYG+Y3U/dLjhTj4Hg1VtCHWRm3nMOE9cVpMJRhRzKhkq6gnZmni8obz2BBDF02X34oQLcHC/Wn8F3E8RiBjZDI66g+iZeCCUXvYz0vxWAQQKBgQDEJu6flyHPvyBPAC4EOxZAw0zh6SF/r8VgjbKO3n/8d+kZJeVmYnbsLodIEEyXQnr35o2CLqhCvR2kstsRSfRz79nMIt6aPWuwYkXNHQGE8rnCxxyJmxV4S63GczLk7SIn4KmqPlCI08AU0TXJS3zwh7O6e6kBljjPt1mnMgvr3QKBgQD6fAkdI0FRZSXwzygx4uSg47Co6X6ESZ9FDf6ph63lvSK5/eue/ugX6p/olMYq5CHXbLpgM4EJYdRfrH6pwqtBwUJhlh1xI6C48nonnw+oh8YPlFCDLxNG4tq6JVo071qH6CFXCIank3ThZeW5a3ZSe5pBZ8h4bUZ9H8pJL4C7yQKBgFb8SN/+/qCJSoOeOcnohhLMSSD56MAeK7KIxAF1jF5isr1TP+rqiYBtldKQX9bIRY3/8QslM7r88NNj+aAuIrjzSausXvkZedMrkXbHgS/7EAPflrkzTA8fyH10AsLgoj/68mKr5bz34nuY13hgAJUOKNbvFeC9RI5g6eIqYH0FAoGAVqFTXZp12rrK1nAvDKHWRLa6wJCQyxvTU8S1UNi2EgDJ492oAgNTLgJdb8kUiH0CH0lhZCgr9py5IKW94OSM6l72oF2UrS6PRafHC7D9b2IV5Al9lwFO/3MyBrMocapeeyaTcVBnkclz4Qim3OwHrhtFjF1ifhP9DwVRpuIg+dECgYANwlHxLe//tr6BM31PUUrOxP5Y/cj+ydxqM/z6papZFkK6Mvi/vMQQNQkh95GH9zqyC5Z/yLxur4ry1eNYty/9FnuZRAkEmlUSZ/DobhU0Pmj8Hep6JsTuMutref6vCk2n02jc9qYmJuD7iXkdXDSawbEG6f5C4MUkJ38z1t1OjA==`)
    31  	privKey   crypto.PrivKey
    32  	profileID = "QmZePf5LeXow3RW5U1AgEiNbW46YnRGhZ7HPvm1UmPFPwt"
    33  
    34  	testPeerProfile = &profile.Profile{
    35  		Peername: "peer",
    36  		ID:       profile.IDB58MustDecode(profileID),
    37  	}
    38  )
    39  
    40  func init() {
    41  	data, err := base64.StdEncoding.DecodeString(string(testPk))
    42  	if err != nil {
    43  		panic(err)
    44  	}
    45  	testPk = data
    46  
    47  	privKey, err = crypto.UnmarshalPrivateKey(testPk)
    48  	if err != nil {
    49  		panic(fmt.Errorf("error unmarshaling private key: %s", err.Error()))
    50  	}
    51  	testPeerProfile.PrivKey = privKey
    52  }
    53  
    54  // TestdataPath returns the absolute path to a file in the testdata diretory
    55  func TestdataPath(path string) string {
    56  	// Get the testdata directory relative to this source file.
    57  	_, currfile, _, _ := runtime.Caller(0)
    58  	testdataPath := filepath.Join(filepath.Dir(currfile), "testdata")
    59  	return filepath.Join(testdataPath, path)
    60  }
    61  
    62  // ProfileConfig returns the test profile as a config.Profile
    63  func ProfileConfig() *config.ProfilePod {
    64  	return &config.ProfilePod{
    65  		Peername: "peer",
    66  		ID:       profileID,
    67  		PrivKey:  string(_testPk),
    68  		Type:     "peer",
    69  	}
    70  }
    71  
    72  // NewEmptyTestRepo initializes a test repo with no contents
    73  func NewEmptyTestRepo(bus event.Bus) (mr *repo.MemRepo, err error) {
    74  	ctx := context.TODO()
    75  	pro := &profile.Profile{
    76  		Peername: "peer",
    77  		ID:       profile.IDB58MustDecode(profileID),
    78  		PrivKey:  privKey,
    79  	}
    80  	return repo.NewMemRepoWithProfile(ctx, pro, newTestFS(ctx), bus)
    81  }
    82  
    83  func newTestFS(ctx context.Context) *muxfs.Mux {
    84  	fs, err := muxfs.New(ctx, []qfs.Config{
    85  		{Type: "mem"},
    86  		{Type: "local"},
    87  		{Type: "http"},
    88  	})
    89  	if err != nil {
    90  		panic(err)
    91  	}
    92  	return fs
    93  }
    94  
    95  // NewTestRepo generates a repository usable for testing purposes
    96  func NewTestRepo() (mr *repo.MemRepo, err error) {
    97  	datasets := []string{"movies", "cities", "counter", "craigslist", "sitemap"}
    98  
    99  	mr, err = NewEmptyTestRepo(event.NilBus)
   100  	if err != nil {
   101  		return
   102  	}
   103  
   104  	for _, dsDirName := range datasets {
   105  		tc, err := dstest.NewTestCaseFromDir(TestdataPath(dsDirName))
   106  		if err != nil {
   107  			return nil, err
   108  		}
   109  		if _, err := createDataset(mr, tc); err != nil {
   110  			return nil, fmt.Errorf("%s error creating dataset: %s", dsDirName, err.Error())
   111  		}
   112  	}
   113  
   114  	return
   115  }
   116  
   117  // NewTestRepoWithHistory generates a repository with a dataset that has a history, usable for testing purposes
   118  func NewTestRepoWithHistory() (mr *repo.MemRepo, refs []reporef.DatasetRef, err error) {
   119  	datasets := []string{"movies", "cities", "counter", "craigslist", "sitemap"}
   120  
   121  	mr, err = NewEmptyTestRepo(event.NilBus)
   122  	if err != nil {
   123  		return
   124  	}
   125  
   126  	prevPath := ""
   127  	for _, dsDirName := range datasets {
   128  		tc, err := dstest.NewTestCaseFromDir(TestdataPath(dsDirName))
   129  		if err != nil {
   130  			return nil, nil, err
   131  		}
   132  		tc.Input.Name = "logtest"
   133  		tc.Input.PreviousPath = prevPath
   134  		ref, err := createDataset(mr, tc)
   135  		if err != nil {
   136  			return nil, nil, fmt.Errorf("%s error creating dataset: %s", dsDirName, err.Error())
   137  		}
   138  		prevPath = ref.Path
   139  		refs = append(refs, ref)
   140  	}
   141  
   142  	// return refs with the first ref as the head of the log
   143  	for i := len(refs)/2 - 1; i >= 0; i-- {
   144  		opp := len(refs) - 1 - i
   145  		refs[i], refs[opp] = refs[opp], refs[i]
   146  	}
   147  
   148  	return
   149  }
   150  
   151  // NewTestRepoFromProfileID constructs a repo from a profileID, usable for tests
   152  func NewTestRepoFromProfileID(id profile.ID, peerNum int, dataIndex int) (repo.Repo, error) {
   153  	ctx := context.TODO()
   154  	datasets := []string{"movies", "cities", "counter", "craigslist", "sitemap"}
   155  
   156  	pk, _, err := crypto.GenerateSecp256k1Key(rand.Reader)
   157  	if err != nil {
   158  		return nil, err
   159  	}
   160  
   161  	r, err := repo.NewMemRepoWithProfile(ctx, &profile.Profile{
   162  		ID:       id,
   163  		Peername: fmt.Sprintf("test-repo-%d", peerNum),
   164  		PrivKey:  pk,
   165  	}, newTestFS(ctx), event.NilBus)
   166  	if err != nil {
   167  		return r, err
   168  	}
   169  
   170  	if dataIndex == -1 || dataIndex >= len(datasets) {
   171  		return r, nil
   172  	}
   173  
   174  	tc, err := dstest.NewTestCaseFromDir(TestdataPath(datasets[dataIndex]))
   175  	if err != nil {
   176  		return r, err
   177  	}
   178  
   179  	if _, err := createDataset(r, tc); err != nil {
   180  		return nil, fmt.Errorf("error creating dataset: %s", err.Error())
   181  	}
   182  	return r, nil
   183  }
   184  
   185  // it's tempting to use base.CreateDataset here, but we can't b/c import cycle :/
   186  // this version of createDataset doesn't run transforms or prepare viz. Test cases
   187  // should be designed to avoid requiring Tranforms be run or Viz be prepped
   188  func createDataset(r repo.Repo, tc dstest.TestCase) (ref reporef.DatasetRef, err error) {
   189  	var (
   190  		ctx = context.Background()
   191  		ds  = tc.Input
   192  		pro = r.Profiles().Owner(ctx)
   193  		// NOTE - struct fields need to be instantiated to make assign set to
   194  		// new pointer values
   195  		userSet = &dataset.Dataset{
   196  			Commit:    &dataset.Commit{},
   197  			Meta:      &dataset.Meta{},
   198  			Structure: &dataset.Structure{},
   199  			Transform: &dataset.Transform{},
   200  			Viz:       &dataset.Viz{},
   201  		}
   202  		path    string
   203  		resBody qfs.File
   204  		dsName  = ds.Name
   205  	)
   206  
   207  	userSet.Assign(ds)
   208  
   209  	if ds.Commit != nil {
   210  		// NOTE: add author ProfileID here to keep the dataset package agnostic to
   211  		// all identity stuff except keypair crypto
   212  		ds.Commit.Author = &dataset.User{ID: pro.ID.Encode()}
   213  	}
   214  
   215  	sw := dsfs.SaveSwitches{Pin: true, ShouldRender: true}
   216  	fs := r.Filesystem()
   217  	if path, err = dsfs.CreateDataset(ctx, fs, fs.DefaultWriteFS(), r.Bus(), ds, nil, pro.PrivKey, sw); err != nil {
   218  		return
   219  	}
   220  	if ds.PreviousPath != "" && ds.PreviousPath != "/" {
   221  		prev := reporef.DatasetRef{
   222  			ProfileID: pro.ID,
   223  			Peername:  pro.Peername,
   224  			Name:      dsName,
   225  			Path:      ds.PreviousPath,
   226  		}
   227  
   228  		// should be ok to skip this error. we may not have the previous
   229  		// reference locally
   230  		_ = r.DeleteRef(prev)
   231  	}
   232  	ref = reporef.DatasetRef{
   233  		ProfileID: pro.ID,
   234  		Peername:  pro.Peername,
   235  		Name:      dsName,
   236  		Path:      path,
   237  	}
   238  
   239  	if err = r.PutRef(ref); err != nil {
   240  		return
   241  	}
   242  
   243  	// TODO (b5): confirm these assignments happen in dsfs.CreateDataset with tests
   244  	ds.ProfileID = pro.ID.Encode()
   245  	ds.Peername = pro.Peername
   246  	ds.Path = path
   247  
   248  	// TODO(dustmop): When we switch to initIDs, use the standard resolver instead of logbook.
   249  	// Whether there is a previous version is equivalent to whether we have an initID coming
   250  	// into this function.
   251  	initID, err := r.Logbook().RefToInitID(dsref.Ref{Username: ds.Peername, Name: dsName})
   252  	if err == logbook.ErrNotFound {
   253  		// If dataset does not exist yet, initialize with the given name
   254  		initID, err = r.Logbook().WriteDatasetInit(ctx, r.Logbook().Owner(), dsName)
   255  		if err != nil {
   256  			return ref, err
   257  		}
   258  	}
   259  	ds.ID = initID
   260  	if err = r.Logbook().WriteVersionSave(ctx, r.Logbook().Owner(), ds, nil); err != nil && err != logbook.ErrNoLogbook {
   261  		return
   262  	}
   263  
   264  	ds, err = dsfs.LoadDataset(ctx, r.Filesystem(), ref.Path)
   265  	if err != nil {
   266  		return ref, err
   267  	}
   268  	ds.Name = ref.Name
   269  	ds.Peername = ref.Peername
   270  	ref.Dataset = ds
   271  
   272  	// need to open here b/c we might be doing a dry-run, which would mean we have
   273  	// references to files in a store that won't exist after this function call
   274  	// TODO (b5): this should be replaced with a call to OpenDataset with a qfs that
   275  	// knows about the store
   276  	if resBody, err = r.Filesystem().Get(ctx, ref.Dataset.BodyPath); err != nil {
   277  		return ref, err
   278  	}
   279  	ref.Dataset.SetBodyFile(resBody)
   280  
   281  	return ref, nil
   282  }
   283  
   284  // NewMemRepoFromDir reads a director of testCases and calls createDataset
   285  // on each case with the given privatekey, yeilding a repo where the peer with
   286  // this pk has created each dataset in question
   287  func NewMemRepoFromDir(path string) (repo.Repo, crypto.PrivKey, error) {
   288  	ctx := context.TODO()
   289  	// TODO (b5) - use a function & a contstant to get this path
   290  	cfgPath := filepath.Join(path, "config.yaml")
   291  
   292  	cfg, err := config.ReadFromFile(cfgPath)
   293  	if err != nil {
   294  		return nil, nil, err
   295  	}
   296  
   297  	pro, err := profile.NewProfile(cfg.Profile)
   298  	if err != nil {
   299  		return nil, nil, err
   300  	}
   301  
   302  	mr, err := repo.NewMemRepoWithProfile(ctx, pro, newTestFS(ctx), event.NilBus)
   303  	if err != nil {
   304  		return mr, pro.PrivKey, err
   305  	}
   306  
   307  	tc, err := dstest.LoadTestCases(path)
   308  	if err != nil {
   309  		return mr, pro.PrivKey, err
   310  	}
   311  
   312  	for _, c := range tc {
   313  		if _, err := createDataset(mr, c); err != nil {
   314  			return mr, pro.PrivKey, err
   315  		}
   316  	}
   317  
   318  	return mr, pro.PrivKey, nil
   319  }
   320  
   321  // BadBodyFile is a bunch of bad CSV data
   322  var BadBodyFile = qfs.NewMemfileBytes("bad_csv_file.csv", []byte(`
   323  asdlkfasd,,
   324  fm as
   325  f;lajsmf 
   326  a
   327  's;f a'
   328  sdlfj asdf`))
   329  
   330  // BadDataFormatFile has weird line lengths
   331  var BadDataFormatFile = qfs.NewMemfileBytes("abc.csv", []byte(`
   332  "colA","colB","colC","colD"
   333  1,2,3,4
   334  1,2,3`))
   335  
   336  // BadStructureFile has double-named columns
   337  var BadStructureFile = qfs.NewMemfileBytes("badStructure.csv", []byte(`
   338  colA, colB, colB, colC
   339  1,2,3,4
   340  1,2,3,4`))