github.com/qri-io/qri@v0.10.1-0.20220104210721-c771715036cb/cmd/search_test.go (about) 1 package cmd 2 3 import ( 4 "bytes" 5 "context" 6 "io/ioutil" 7 "net/http" 8 "net/http/httptest" 9 "os" 10 "testing" 11 12 "github.com/qri-io/ioes" 13 "github.com/qri-io/qri/errors" 14 "github.com/qri-io/qri/lib" 15 "github.com/qri-io/qri/registry/regclient" 16 ) 17 18 func TestSearchComplete(t *testing.T) { 19 run := NewTestRunner(t, "test_peer_search_complete", "qri_test_search_complete") 20 defer run.Delete() 21 22 ctx, cancel := context.WithCancel(context.Background()) 23 defer cancel() 24 25 f, err := NewTestFactory(ctx) 26 if err != nil { 27 t.Errorf("error creating new test factory: %s", err) 28 return 29 } 30 31 cases := []struct { 32 args []string 33 expect string 34 err string 35 }{ 36 {[]string{}, "", ""}, 37 {[]string{"test"}, "test", ""}, 38 {[]string{"test", "test2"}, "test", ""}, 39 } 40 41 for i, c := range cases { 42 opt := &SearchOptions{ 43 IOStreams: run.Streams, 44 } 45 46 opt.Complete(f, c.args) 47 48 if c.err != run.ErrStream.String() { 49 t.Errorf("case %d, error mismatch. Expected: '%s', Got: '%s'", i, c.err, run.ErrStream.String()) 50 run.IOReset() 51 continue 52 } 53 54 if c.expect != opt.Query { 55 t.Errorf("case %d, opt.Ref not set correctly. Expected: '%s', Got: '%s'", i, c.expect, opt.Query) 56 run.IOReset() 57 continue 58 } 59 60 run.IOReset() 61 } 62 } 63 64 func TestSearchValidate(t *testing.T) { 65 cases := []struct { 66 query string 67 err string 68 msg string 69 }{ 70 {"test", "", ""}, 71 {"", lib.ErrBadArgs.Error(), "please provide search parameters, for example:\n $ qri search census\n $ qri search 'census 2018'\nsee `qri search --help` for more information"}, 72 } 73 for i, c := range cases { 74 opt := &SearchOptions{ 75 Query: c.query, 76 } 77 78 err := opt.Validate() 79 if (err == nil && c.err != "") || (err != nil && c.err != err.Error()) { 80 t.Errorf("case %d, mismatched error. Expected: %s, Got: %s", i, c.err, err) 81 continue 82 } 83 if libErr, ok := err.(errors.Error); ok { 84 if libErr.Message() != c.msg { 85 t.Errorf("case %d, mismatched user-friendly message. Expected: '%s', Got: '%s'", i, c.msg, libErr.Message()) 86 continue 87 } 88 } else if c.msg != "" { 89 t.Errorf("case %d, mismatched user-friendly message. Expected: '%s', Got: ''", i, c.msg) 90 continue 91 } 92 } 93 } 94 95 // SearchTestRunner holds state used by the search test 96 // TODO(dustmop): Compose this with TestRunner instead 97 type SearchTestRunner struct { 98 Pwd string 99 RootPath string 100 Teardown func() 101 Streams ioes.IOStreams 102 InStream *bytes.Buffer 103 OutStream *bytes.Buffer 104 ErrStream *bytes.Buffer 105 } 106 107 // NewSearchTestRunner sets up state needed for the search test 108 // TODO (b5) - add an explicit RepoPath to the SearchTestRunner. Tests are 109 // relying on the "RootPath" property, which should be configurable per-test 110 func NewSearchTestRunner(t *testing.T) *SearchTestRunner { 111 run := SearchTestRunner{} 112 113 // Set IOStreams 114 run.Streams, run.InStream, run.OutStream, run.ErrStream = ioes.NewTestIOStreams() 115 116 // Get current directory 117 var err error 118 run.Pwd, err = os.Getwd() 119 if err != nil { 120 t.Fatal(err) 121 } 122 123 // Create temporary directory to run the test in 124 run.RootPath, err = ioutil.TempDir("", "") 125 if err != nil { 126 t.Fatal(err) 127 } 128 os.Chdir(run.RootPath) 129 130 // Clean up function 131 run.Teardown = func() { 132 os.Chdir(run.Pwd) 133 os.RemoveAll(run.RootPath) 134 } 135 return &run 136 } 137 138 // Close tears down the test 139 func (r *SearchTestRunner) Close() { 140 r.Teardown() 141 } 142 143 // IOReset resets the io streams 144 func (r *SearchTestRunner) IOReset() { 145 r.InStream.Reset() 146 r.OutStream.Reset() 147 r.ErrStream.Reset() 148 } 149 150 func TestSearchRun(t *testing.T) { 151 run := NewSearchTestRunner(t) 152 defer run.Close() 153 154 ctx, cancel := context.WithCancel(context.Background()) 155 defer cancel() 156 setNoColor(true) 157 158 // mock registry server that returns cached response data 159 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 160 w.Write(mockResponse) 161 })) 162 rc := regclient.NewClient(®client.Config{Location: server.URL}) 163 164 f, err := NewTestFactoryInstanceOptions(ctx, run.RootPath, lib.OptRegistryClient(rc)) 165 if err != nil { 166 t.Fatal(err) 167 } 168 inst, err := f.Instance() 169 if err != nil { 170 t.Fatal(err) 171 } 172 173 cases := []struct { 174 query string 175 format string 176 expected string 177 err string 178 msg string 179 }{ 180 {"test", "", textSearchResponse, "", ""}, 181 {"test", "json", jsonSearchResponse, "", ""}, 182 } 183 184 for i, c := range cases { 185 opt := &SearchOptions{ 186 IOStreams: run.Streams, 187 Query: c.query, 188 Format: c.format, 189 Instance: inst, 190 } 191 192 err = opt.Run() 193 194 if (err == nil && c.err != "") || (err != nil && c.err != err.Error()) { 195 t.Errorf("case %d, mismatched error. Expected: '%s', Got: '%v'", i, c.err, err) 196 run.IOReset() 197 continue 198 } 199 200 if libErr, ok := err.(errors.Error); ok { 201 if libErr.Message() != c.msg { 202 t.Errorf("case %d, mismatched user-friendly message. Expected: '%s', Got: '%s'", i, c.msg, libErr.Message()) 203 run.IOReset() 204 continue 205 } 206 } else if c.msg != "" { 207 t.Errorf("case %d, mismatched user-friendly message. Expected: '%s', Got: ''", i, c.msg) 208 run.IOReset() 209 continue 210 } 211 212 if c.expected != run.OutStream.String() { 213 t.Errorf("case %d, output mismatch. Expected: '%s', Got: '%s'", i, c.expected, run.OutStream.String()) 214 run.IOReset() 215 continue 216 } 217 run.IOReset() 218 } 219 } 220 221 var mockResponse = []byte(`{"data":[ 222 { 223 "type": "dataset", 224 "id": "/ipfs/QmZEnjt3Y5RxXsoZyufJfFzcogicBEwfaimJSyDuC7nySA", 225 "value": { 226 "commit": { 227 "qri": "cm:0", 228 "timestamp": "2019-08-31T12:07:56.212858Z", 229 "title": "change to 10" 230 }, 231 "meta": { 232 "keywords": [ 233 "joke" 234 ], 235 "qri": "md:0", 236 "title": "this is a d" 237 }, 238 "name": "nuun", 239 "path": "/ipfs/QmZEnjt3Y5RxXsoZyufJfFzcogicBEwfaimJSyDuC7nySA", 240 "peername": "nuun", 241 "qri": "ds:0", 242 "structure": { 243 "entries": 3, 244 "format": "csv", 245 "length": 36, 246 "qri": "st:0" 247 } 248 } 249 } 250 ],"meta":{"code":200}}`) 251 252 var textSearchResponse = `showing 1 results for 'test' 253 1 nuun/nuun 254 /ipfs/QmZEnjt3Y5RxXsoZyufJfFzcogicBEwfaimJSyDuC7nySA 255 this is a d 256 36 B, 3 entries, 0 errors 257 258 ` 259 260 var jsonSearchResponse = `[ 261 { 262 "type": "dataset", 263 "id": "/ipfs/QmZEnjt3Y5RxXsoZyufJfFzcogicBEwfaimJSyDuC7nySA", 264 "url": "", 265 "value": { 266 "commit": { 267 "qri": "cm:0", 268 "timestamp": "2019-08-31T12:07:56.212858Z", 269 "title": "change to 10" 270 }, 271 "meta": { 272 "keywords": [ 273 "joke" 274 ], 275 "qri": "md:0", 276 "title": "this is a d" 277 }, 278 "name": "nuun", 279 "path": "/ipfs/QmZEnjt3Y5RxXsoZyufJfFzcogicBEwfaimJSyDuC7nySA", 280 "peername": "nuun", 281 "qri": "ds:0", 282 "structure": { 283 "entries": 3, 284 "format": "csv", 285 "length": 36, 286 "qri": "st:0" 287 } 288 } 289 } 290 ]`