fork of https://github.com/sourcegraph/zoekt
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}