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`))