fork of https://github.com/sourcegraph/zoekt
0

Configure Feed

Select the types of activity you want to include in your feed.

1// Copyright 2016 Google Inc. All rights reserved. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package web 16 17import ( 18 "bytes" 19 "context" 20 "encoding/json" 21 "fmt" 22 "io" 23 "log" 24 "net/http" 25 "net/http/httptest" 26 "reflect" 27 "strings" 28 "testing" 29 "time" 30 31 "github.com/google/go-cmp/cmp" 32 "github.com/sourcegraph/zoekt" 33 "github.com/sourcegraph/zoekt/query" 34) 35 36// TODO(hanwen): cut & paste from ../ . Should create internal test 37// util package. 38type memSeeker struct { 39 data []byte 40} 41 42func (s *memSeeker) Close() {} 43func (s *memSeeker) Read(off, sz uint32) ([]byte, error) { 44 return s.data[off : off+sz], nil 45} 46 47func (s *memSeeker) Size() (uint32, error) { 48 return uint32(len(s.data)), nil 49} 50 51func (s *memSeeker) Name() string { 52 return "memSeeker" 53} 54 55func searcherForTest(t *testing.T, b *zoekt.IndexBuilder) zoekt.Streamer { 56 var buf bytes.Buffer 57 if err := b.Write(&buf); err != nil { 58 t.Fatal(err) 59 } 60 f := &memSeeker{buf.Bytes()} 61 62 searcher, err := zoekt.NewSearcher(f) 63 if err != nil { 64 t.Fatalf("NewSearcher: %v", err) 65 } 66 67 return adapter{Searcher: searcher} 68} 69 70type adapter struct { 71 zoekt.Searcher 72} 73 74func (a adapter) StreamSearch(ctx context.Context, q query.Q, opts *zoekt.SearchOptions, sender zoekt.Sender) (err error) { 75 sr, err := a.Searcher.Search(ctx, q, opts) 76 if err != nil { 77 return err 78 } 79 sender.Send(sr) 80 return nil 81} 82 83func TestBasic(t *testing.T) { 84 b, err := zoekt.NewIndexBuilder(&zoekt.Repository{ 85 Name: "name", 86 URL: "repo-url", 87 CommitURLTemplate: "{{.Version}}", 88 FileURLTemplate: "file-url", 89 LineFragmentTemplate: "#line", 90 Branches: []zoekt.RepositoryBranch{{Name: "master", Version: "1234"}}, 91 }) 92 if err != nil { 93 t.Fatalf("NewIndexBuilder: %v", err) 94 } 95 if err := b.Add(zoekt.Document{ 96 Name: "f2", 97 Content: []byte("to carry water in the no later bla"), 98 // --------------0123456789012345678901234567890123 99 // --------------0 1 2 3 100 Branches: []string{"master"}, 101 }); err != nil { 102 t.Fatalf("Add: %v", err) 103 } 104 105 s := searcherForTest(t, b) 106 srv := Server{ 107 Searcher: s, 108 Top: Top, 109 HTML: true, 110 } 111 112 mux, err := NewMux(&srv) 113 if err != nil { 114 t.Fatalf("NewMux: %v", err) 115 } 116 117 ts := httptest.NewServer(mux) 118 defer ts.Close() 119 120 nowStr := time.Now().UTC().Format("Jan 02, 2006 15:04") 121 for req, needles := range map[string][]string{ 122 "/": {"from 1 repositories"}, 123 "/search?q=water": { 124 "href=\"file-url#line", 125 "carry <b>water</b>", 126 }, 127 "/search?q=r:": { 128 "1234\">master", 129 "Found 1 repositories", 130 nowStr, 131 "repo-url\">name", 132 "1 files (36B)", 133 }, 134 "/search?q=magic": { 135 `value=magic`, 136 }, 137 "/robots.txt": { 138 "disallow: /search", 139 }, 140 } { 141 checkNeedles(t, ts, req, needles) 142 } 143} 144 145func TestPrint(t *testing.T) { 146 b, err := zoekt.NewIndexBuilder(&zoekt.Repository{ 147 Name: "name", 148 URL: "repo-url", 149 CommitURLTemplate: "{{.Version}}", 150 FileURLTemplate: "file-url", 151 LineFragmentTemplate: "line", 152 Branches: []zoekt.RepositoryBranch{{Name: "master", Version: "1234"}}, 153 }) 154 if err != nil { 155 t.Fatalf("NewIndexBuilder: %v", err) 156 } 157 if err := b.Add(zoekt.Document{ 158 Name: "f2", 159 Content: []byte("to carry water in the no later bla"), 160 Branches: []string{"master"}, 161 }); err != nil { 162 t.Fatalf("Add: %v", err) 163 } 164 165 if err := b.Add(zoekt.Document{ 166 Name: "dir/f2", 167 Content: []byte("blabla"), 168 Branches: []string{"master"}, 169 }); err != nil { 170 t.Fatalf("Add: %v", err) 171 } 172 173 s := searcherForTest(t, b) 174 srv := Server{ 175 Searcher: s, 176 Top: Top, 177 HTML: true, 178 Print: true, 179 } 180 181 mux, err := NewMux(&srv) 182 if err != nil { 183 t.Fatalf("NewMux: %v", err) 184 } 185 186 ts := httptest.NewServer(mux) 187 defer ts.Close() 188 189 for req, needles := range map[string][]string{ 190 "/print?q=bla&r=name&f=f2": { 191 `pre id="l1" class="inline-pre"><span class="noselect"><a href="#l1">`, 192 }, 193 } { 194 checkNeedles(t, ts, req, needles) 195 } 196} 197 198func TestPrintDefault(t *testing.T) { 199 b, err := zoekt.NewIndexBuilder(&zoekt.Repository{ 200 Name: "name", 201 URL: "repo-url", 202 Branches: []zoekt.RepositoryBranch{{Name: "master", Version: "1234"}}, 203 }) 204 if err != nil { 205 t.Fatalf("NewIndexBuilder: %v", err) 206 } 207 if err := b.Add(zoekt.Document{ 208 Name: "f2", 209 Content: []byte("to carry water in the no later bla"), 210 Branches: []string{"master"}, 211 }); err != nil { 212 t.Fatalf("Add: %v", err) 213 } 214 s := searcherForTest(t, b) 215 srv := Server{ 216 Searcher: s, 217 Top: Top, 218 HTML: true, 219 } 220 221 mux, err := NewMux(&srv) 222 if err != nil { 223 t.Fatalf("NewMux: %v", err) 224 } 225 226 ts := httptest.NewServer(mux) 227 defer ts.Close() 228 229 for req, needles := range map[string][]string{ 230 "/search?q=water": { 231 `href="print?`, 232 }, 233 } { 234 checkNeedles(t, ts, req, needles) 235 } 236} 237 238func checkNeedles(t *testing.T, ts *httptest.Server, req string, needles []string) { 239 res, err := http.Get(ts.URL + req) 240 if err != nil { 241 t.Fatal(err) 242 } 243 resultBytes, err := io.ReadAll(res.Body) 244 res.Body.Close() 245 if err != nil { 246 log.Fatal(err) 247 } 248 249 result := string(resultBytes) 250 for _, want := range needles { 251 if !strings.Contains(result, want) { 252 t.Errorf("query %q: result did not have %q: %s", req, want, result) 253 } 254 } 255 if notWant := "crashed"; strings.Contains(result, notWant) { 256 t.Errorf("result has %q: %s", notWant, result) 257 } 258 if notWant := "bytes skipped)..."; strings.Contains(result, notWant) { 259 t.Errorf("result has %q: %s", notWant, result) 260 } 261} 262 263type Expectation struct { 264 title string 265 fileMatch FileMatch 266} 267 268func TestFormatJson(t *testing.T) { 269 b, err := zoekt.NewIndexBuilder(&zoekt.Repository{ 270 Name: "name", 271 URL: "repo-url", 272 Branches: []zoekt.RepositoryBranch{{Name: "master", Version: "1234"}}, 273 }) 274 if err != nil { 275 t.Fatalf("NewIndexBuilder: %v", err) 276 } 277 if err := b.Add(zoekt.Document{ 278 Name: "f2", 279 Content: []byte("to carry water in the no later bla"), 280 Branches: []string{"master"}, 281 }); err != nil { 282 t.Fatalf("Add: %v", err) 283 } 284 s := searcherForTest(t, b) 285 srv := Server{ 286 Searcher: s, 287 Top: Top, 288 HTML: true, 289 } 290 291 mux, err := NewMux(&srv) 292 if err != nil { 293 t.Fatalf("NewMux: %v", err) 294 } 295 296 ts := httptest.NewServer(mux) 297 defer ts.Close() 298 299 expected := Expectation{ 300 "json basic test", 301 FileMatch{ 302 FileName: "f2", 303 Repo: "name", 304 Matches: []Match{ 305 { 306 FileName: "f2", 307 LineNum: 1, 308 Fragments: []Fragment{ 309 { 310 Pre: "to carry ", 311 Match: "water", 312 Post: " in the no later bla", 313 }, 314 }, 315 }, 316 }, 317 }, 318 } 319 320 checkResultMatches(t, ts, "/search?q=water&format=json", expected) 321} 322 323func TestContextLines(t *testing.T) { 324 b, err := zoekt.NewIndexBuilder(&zoekt.Repository{ 325 Name: "name", 326 URL: "repo-url", 327 Branches: []zoekt.RepositoryBranch{{Name: "master", Version: "1234"}}, 328 }) 329 if err != nil { 330 t.Fatalf("NewIndexBuilder: %v", err) 331 } 332 if err := b.Add(zoekt.Document{ 333 Name: "f2", 334 Content: []byte("one line\nsecond snippet\nthird thing\nfourth\nfifth block\nsixth example\nseventh"), 335 Branches: []string{"master"}, 336 }); err != nil { 337 t.Fatalf("Add: %v", err) 338 } 339 if err := b.Add(zoekt.Document{ 340 Name: "f3", 341 Content: []byte("\n\n\n\nto carry water in the no later bla\n\n\n\n"), 342 Branches: []string{"master"}, 343 }); err != nil { 344 t.Fatalf("Add: %v", err) 345 } 346 if err := b.Add(zoekt.Document{ 347 Name: "f4", 348 Content: []byte("un \n \n\ttrois\n \n\nsix\n "), 349 Branches: []string{"master"}, 350 }); err != nil { 351 t.Fatalf("Add: %v", err) 352 } 353 if err := b.Add(zoekt.Document{ 354 Name: "f5", 355 Content: []byte("\ngreen\npastures\n\nhere"), 356 Branches: []string{"master"}, 357 }); err != nil { 358 t.Fatalf("Add: %v", err) 359 } 360 s := searcherForTest(t, b) 361 srv := Server{ 362 Searcher: s, 363 Top: Top, 364 HTML: true, 365 } 366 367 mux, err := NewMux(&srv) 368 if err != nil { 369 t.Fatalf("NewMux: %v", err) 370 } 371 372 ts := httptest.NewServer(mux) 373 defer ts.Close() 374 375 for req, expected := range map[string]Expectation{ 376 "/search?q=our&format=json&ctx=0": { 377 "no context doesn't return Before or After", 378 FileMatch{ 379 FileName: "f2", 380 Repo: "name", 381 Matches: []Match{ 382 { 383 FileName: "f2", 384 LineNum: 4, 385 Fragments: []Fragment{ 386 { 387 Pre: "f", 388 Match: "our", 389 Post: "th", 390 }, 391 }, 392 }, 393 }, 394 }, 395 }, 396 "/search?q=f:f2&format=json&ctx=2": { 397 "filename does not return Before or After", 398 FileMatch{ 399 FileName: "f2", 400 Repo: "name", 401 Matches: []Match{ 402 { 403 FileName: "f2", 404 LineNum: 0, 405 Fragments: []Fragment{ 406 { 407 Match: "f2", 408 }, 409 }, 410 }, 411 }, 412 }, 413 }, 414 "/search?q=our&format=json&ctx=2": { 415 "context returns Before and After", 416 FileMatch{ 417 FileName: "f2", 418 Repo: "name", 419 Matches: []Match{ 420 { 421 FileName: "f2", 422 LineNum: 4, 423 Fragments: []Fragment{ 424 { 425 Pre: "f", 426 Match: "our", 427 Post: "th", 428 }, 429 }, 430 Before: "second snippet\nthird thing", 431 After: "fifth block\nsixth example", 432 }, 433 }, 434 }, 435 }, 436 "/search?q=one&format=json&ctx=2": { 437 "match at start returns After but no Before", 438 FileMatch{ 439 FileName: "f2", 440 Repo: "name", 441 Matches: []Match{ 442 { 443 FileName: "f2", 444 LineNum: 1, 445 Fragments: []Fragment{ 446 { 447 Pre: "", 448 Match: "one", 449 Post: " line", 450 }, 451 }, 452 After: "second snippet\nthird thing", 453 }, 454 }, 455 }, 456 }, 457 "/search?q=seventh&format=json&ctx=2": { 458 "match at end returns Before but no After", 459 FileMatch{ 460 FileName: "f2", 461 Repo: "name", 462 Matches: []Match{ 463 { 464 FileName: "f2", 465 LineNum: 7, 466 Fragments: []Fragment{ 467 { 468 Pre: "", 469 Match: "seventh", 470 Post: "", 471 }, 472 }, 473 Before: "fifth block\nsixth example", 474 }, 475 }, 476 }, 477 }, 478 "/search?q=seventh&format=json&ctx=10": { 479 "match with large context at end returns whole document", 480 FileMatch{ 481 FileName: "f2", 482 Repo: "name", 483 Matches: []Match{ 484 { 485 FileName: "f2", 486 LineNum: 7, 487 Fragments: []Fragment{ 488 { 489 Pre: "", 490 Match: "seventh", 491 Post: "", 492 }, 493 }, 494 Before: "one line\nsecond snippet\nthird thing\nfourth\nfifth block\nsixth example", 495 }, 496 }, 497 }, 498 }, 499 "/search?q=one&format=json&ctx=10": { 500 "match with large context at start returns whole document", 501 FileMatch{ 502 FileName: "f2", 503 Repo: "name", 504 Matches: []Match{ 505 { 506 FileName: "f2", 507 LineNum: 1, 508 Fragments: []Fragment{ 509 { 510 Pre: "", 511 Match: "one", 512 Post: " line", 513 }, 514 }, 515 After: "second snippet\nthird thing\nfourth\nfifth block\nsixth example\nseventh", 516 }, 517 }, 518 }, 519 }, 520 "/search?q=trois&format=json&ctx=2": { 521 "context returns whitespaces lines", 522 FileMatch{ 523 FileName: "f4", 524 Repo: "name", 525 Matches: []Match{ 526 { 527 FileName: "f4", 528 LineNum: 3, 529 Fragments: []Fragment{ 530 { 531 Pre: "\t", 532 Match: "trois", 533 }, 534 }, 535 Before: "un \n ", 536 After: " \n", 537 }, 538 }, 539 }, 540 }, 541 "/search?q=water&format=json&ctx=4": { 542 "context returns new lines", 543 FileMatch{ 544 FileName: "f3", 545 Repo: "name", 546 Matches: []Match{ 547 { 548 FileName: "f3", 549 LineNum: 5, 550 Fragments: []Fragment{ 551 { 552 Pre: "to carry ", 553 Match: "water", 554 Post: " in the no later bla", 555 }, 556 }, 557 // Returns 3 instead of 4 new line characters since we swallow 558 // the last new line in Before, Fragments and After. 559 Before: "\n\n\n", 560 After: "\n\n\n", 561 }, 562 }, 563 }, 564 }, 565 "/search?q=pastures&format=json&ctx=1": { 566 "context returns empty end line", 567 FileMatch{ 568 FileName: "f5", 569 Repo: "name", 570 Matches: []Match{ 571 { 572 FileName: "f5", 573 LineNum: 3, 574 Fragments: []Fragment{ 575 { 576 Pre: "", 577 Match: "pastures", 578 }, 579 }, 580 Before: "green", 581 After: "", 582 }, 583 }, 584 }, 585 }, 586 } { 587 checkResultMatches(t, ts, req, expected) 588 } 589} 590 591func matchesPartiallyEqual(a, b []Match) bool { 592 if len(a) != len(b) { 593 return false 594 } 595 for i := range a { 596 if a[i].FileName != b[i].FileName { 597 return false 598 } 599 if a[i].LineNum != b[i].LineNum { 600 return false 601 } 602 if !reflect.DeepEqual(a[i].Before, b[i].Before) { 603 return false 604 } 605 if !reflect.DeepEqual(a[i].After, b[i].After) { 606 return false 607 } 608 if !reflect.DeepEqual(a[i].Fragments, b[i].Fragments) { 609 return false 610 } 611 } 612 return true 613} 614 615func checkResultMatches(t *testing.T, ts *httptest.Server, req string, expected Expectation) { 616 res, err := http.Get(ts.URL + req) 617 if err != nil { 618 t.Fatal(err) 619 } 620 resultBytes, err := io.ReadAll(res.Body) 621 res.Body.Close() 622 if err != nil { 623 log.Fatal(err) 624 } 625 626 var result ApiSearchResult 627 if err := json.Unmarshal(resultBytes, &result); err != nil { 628 log.Fatal(err) 629 } 630 631 if len(result.Result.FileMatches) != 1 { 632 t.Fatalf("Expected search to return just one result but it was %d", len(result.Result.FileMatches)) 633 } 634 match := result.Result.FileMatches[0] 635 if match.FileName == expected.fileMatch.FileName && match.Repo == expected.fileMatch.Repo { 636 if matchesPartiallyEqual(match.Matches, expected.fileMatch.Matches) { 637 return 638 } 639 } 640 641 t.Errorf( 642 "result doesn't match case <%s>:\nDiff:\n %v", 643 expected.title, 644 cmp.Diff(expected.fileMatch.Matches, result.Result.FileMatches[0].Matches)) 645} 646 647func TestContextLinesMustBeValid(t *testing.T) { 648 b, err := zoekt.NewIndexBuilder(&zoekt.Repository{ 649 Name: "name", 650 URL: "repo-url", 651 Branches: []zoekt.RepositoryBranch{{Name: "master", Version: "1234"}}, 652 }) 653 if err != nil { 654 t.Fatalf("NewIndexBuilder: %v", err) 655 } 656 if err := b.Add(zoekt.Document{ 657 Name: "f2", 658 Content: []byte("to carry water in the no later bla"), 659 Branches: []string{"master"}, 660 }); err != nil { 661 t.Fatalf("Add: %v", err) 662 } 663 s := searcherForTest(t, b) 664 srv := Server{ 665 Searcher: s, 666 Top: Top, 667 HTML: true, 668 } 669 670 mux, err := NewMux(&srv) 671 if err != nil { 672 t.Fatalf("NewMux: %v", err) 673 } 674 675 ts := httptest.NewServer(mux) 676 defer ts.Close() 677 678 // Don't care about ctx if format is not json 679 code := getHttpStatusCode(t, ts, "/search?q=water&ctx=10") 680 if code != 200 { 681 t.Errorf("Expected 200 but got %v", code) 682 } 683 684 // ctx must be a valid integer in the right range. 685 for _, want := range []string{"foo", "-1", "20"} { 686 code := getHttpStatusCode(t, ts, "/search?q=water&format=json&ctx="+want) 687 if code != 418 { 688 t.Errorf("Expected 418 but got %v", code) 689 } 690 } 691} 692 693func getHttpStatusCode(t *testing.T, ts *httptest.Server, req string) int { 694 res, err := http.Get(ts.URL + req) 695 if err != nil { 696 t.Fatal(err) 697 } 698 return res.StatusCode 699} 700 701type crashSearcher struct { 702 zoekt.Streamer 703} 704 705func (s *crashSearcher) Search(ctx context.Context, q query.Q, opts *zoekt.SearchOptions) (*zoekt.SearchResult, error) { 706 res := zoekt.SearchResult{} 707 res.Stats.Crashes = 1 708 return &res, nil 709} 710 711func TestCrash(t *testing.T) { 712 srv := Server{ 713 Searcher: &crashSearcher{}, 714 Top: Top, 715 HTML: true, 716 } 717 718 mux, err := NewMux(&srv) 719 if err != nil { 720 t.Fatalf("NewMux: %v", err) 721 } 722 723 ts := httptest.NewServer(mux) 724 defer ts.Close() 725 726 res, err := http.Get(ts.URL + "/search?q=water") 727 if err != nil { 728 t.Fatal(err) 729 } 730 resultBytes, err := io.ReadAll(res.Body) 731 res.Body.Close() 732 if err != nil { 733 t.Fatal(err) 734 } 735 736 result := string(resultBytes) 737 if want := "1 shards crashed"; !strings.Contains(result, want) { 738 t.Errorf("result did not have %q: %s", want, result) 739 } 740} 741 742func TestHostCustomization(t *testing.T) { 743 b, err := zoekt.NewIndexBuilder(&zoekt.Repository{ 744 Name: "name", 745 }) 746 if err != nil { 747 t.Fatalf("NewIndexBuilder: %v", err) 748 } 749 if err := b.Add(zoekt.Document{ 750 Name: "file", 751 Content: []byte("bla"), 752 }); err != nil { 753 t.Fatalf("Add: %v", err) 754 } 755 756 s := searcherForTest(t, b) 757 srv := Server{ 758 Searcher: s, 759 Top: Top, 760 HTML: true, 761 HostCustomQueries: map[string]string{ 762 "myproject.io": "r:myproject", 763 }, 764 } 765 766 mux, err := NewMux(&srv) 767 if err != nil { 768 t.Fatalf("NewMux: %v", err) 769 } 770 771 ts := httptest.NewServer(mux) 772 defer ts.Close() 773 774 req, err := http.NewRequest("GET", ts.URL, &bytes.Buffer{}) 775 if err != nil { 776 t.Fatalf("NewRequest: %v", err) 777 } 778 req.Host = "myproject.io" 779 res, err := (&http.Client{}).Do(req) 780 if err != nil { 781 t.Fatalf("Do(%v): %v", req, err) 782 } 783 resultBytes, err := io.ReadAll(res.Body) 784 res.Body.Close() 785 if err != nil { 786 t.Fatalf("ReadAll: %v", err) 787 } 788 789 if got, want := string(resultBytes), "r:myproject"; !strings.Contains(got, want) { 790 t.Fatalf("got %s, want substring %q", got, want) 791 } 792} 793 794func TestDupResult(t *testing.T) { 795 b, err := zoekt.NewIndexBuilder(&zoekt.Repository{ 796 Name: "name", 797 }) 798 if err != nil { 799 t.Fatalf("NewIndexBuilder: %v", err) 800 } 801 802 for i := 0; i < 2; i++ { 803 if err := b.Add(zoekt.Document{ 804 Name: fmt.Sprintf("file%d", i), 805 Content: []byte("bla"), 806 }); err != nil { 807 t.Fatalf("Add: %v", err) 808 } 809 } 810 s := searcherForTest(t, b) 811 srv := Server{ 812 Searcher: s, 813 Top: Top, 814 HTML: true, 815 } 816 817 mux, err := NewMux(&srv) 818 if err != nil { 819 t.Fatalf("NewMux: %v", err) 820 } 821 822 ts := httptest.NewServer(mux) 823 defer ts.Close() 824 825 req, err := http.NewRequest("GET", ts.URL+"/search?q=bla", &bytes.Buffer{}) 826 if err != nil { 827 t.Fatalf("NewRequest: %v", err) 828 } 829 res, err := (&http.Client{}).Do(req) 830 if err != nil { 831 t.Fatalf("Do(%v): %v", req, err) 832 } 833 resultBytes, err := io.ReadAll(res.Body) 834 res.Body.Close() 835 if err != nil { 836 t.Fatalf("ReadAll: %v", err) 837 } 838 839 if got, want := string(resultBytes), "Duplicate result"; !strings.Contains(got, want) { 840 t.Fatalf("got %s, want substring %q", got, want) 841 } 842} 843 844func TestTruncateLine(t *testing.T) { 845 b, err := zoekt.NewIndexBuilder(&zoekt.Repository{ 846 Name: "name", 847 }) 848 if err != nil { 849 t.Fatalf("NewIndexBuilder: %v", err) 850 } 851 852 largePadding := bytes.Repeat([]byte{'a'}, 100*1000) // 100kb 853 if err := b.Add(zoekt.Document{ 854 Name: "file", 855 Content: append(append(largePadding, []byte("helloworld")...), largePadding...), 856 }); err != nil { 857 t.Fatalf("Add: %v", err) 858 } 859 s := searcherForTest(t, b) 860 srv := Server{ 861 Searcher: s, 862 Top: Top, 863 HTML: true, 864 } 865 866 mux, err := NewMux(&srv) 867 if err != nil { 868 t.Fatalf("NewMux: %v", err) 869 } 870 871 ts := httptest.NewServer(mux) 872 defer ts.Close() 873 874 req, err := http.NewRequest("GET", ts.URL+"/search?q=helloworld", &bytes.Buffer{}) 875 if err != nil { 876 t.Fatalf("NewRequest: %v", err) 877 } 878 res, err := (&http.Client{}).Do(req) 879 if err != nil { 880 t.Fatalf("Do(%v): %v", req, err) 881 } 882 resultBytes, err := io.ReadAll(res.Body) 883 res.Body.Close() 884 if err != nil { 885 t.Fatalf("ReadAll: %v", err) 886 } 887 888 if got, want := len(resultBytes)/1000, 10; got > want { 889 t.Fatalf("got %dkb response, want <= %dkb", got, want) 890 } 891 result := string(resultBytes) 892 if want := "aa<b>helloworld</b>aa"; !strings.Contains(result, want) { 893 t.Fatalf("got %s, want substring %q", result, want) 894 } 895 if want := "bytes skipped)..."; !strings.Contains(result, want) { 896 t.Fatalf("got %s, want substring %q", result, want) 897 } 898} 899 900func TestHealthz(t *testing.T) { 901 b, err := zoekt.NewIndexBuilder(&zoekt.Repository{ 902 Name: "name", 903 }) 904 if err != nil { 905 t.Fatalf("NewIndexBuilder: %v", err) 906 } 907 908 for i := 0; i < 2; i++ { 909 if err := b.Add(zoekt.Document{ 910 Name: fmt.Sprintf("file%d", i), 911 Content: []byte("bla"), 912 }); err != nil { 913 t.Fatalf("Add: %v", err) 914 } 915 } 916 s := searcherForTest(t, b) 917 srv := Server{ 918 Searcher: s, 919 Top: Top, 920 HTML: true, 921 } 922 923 mux, err := NewMux(&srv) 924 if err != nil { 925 t.Fatalf("NewMux: %v", err) 926 } 927 928 ts := httptest.NewServer(mux) 929 t.Cleanup(ts.Close) 930 931 req, err := http.NewRequest("GET", ts.URL+"/healthz", nil) 932 if err != nil { 933 t.Fatalf("NewRequest: %v", err) 934 } 935 res, err := http.DefaultClient.Do(req) 936 if err != nil { 937 t.Fatalf("Do(%v): %v", req, err) 938 } 939 940 t.Cleanup(func() { 941 res.Body.Close() 942 }) 943 944 if res.StatusCode != http.StatusOK { 945 t.Fatalf("want 200 status code, got: %v", res.StatusCode) 946 } 947 948 var result zoekt.SearchResult 949 err = json.NewDecoder(res.Body).Decode(&result) 950 if err != nil { 951 t.Fatalf("json.Decode: %v", err) 952 } 953 954 if reflect.DeepEqual(result, zoekt.SearchResult{}) { 955 t.Fatal("empty result in response") 956 } 957}