fork of https://github.com/sourcegraph/zoekt
1package main
2
3import (
4 "context"
5 "errors"
6 "fmt"
7 "net/url"
8 "os"
9 "os/exec"
10 "path/filepath"
11 "sort"
12 "strings"
13 "testing"
14 "time"
15
16 "github.com/sourcegraph/log/logtest"
17 proto "github.com/sourcegraph/zoekt/cmd/zoekt-sourcegraph-indexserver/protos/sourcegraph/zoekt/configuration/v1"
18 "github.com/sourcegraph/zoekt/ctags"
19 "google.golang.org/grpc"
20 "google.golang.org/protobuf/testing/protocmp"
21 "google.golang.org/protobuf/types/known/timestamppb"
22
23 "github.com/google/go-cmp/cmp"
24 "github.com/google/go-cmp/cmp/cmpopts"
25
26 "github.com/sourcegraph/zoekt"
27)
28
29func TestIterateIndexOptions_Fingerprint(t *testing.T) {
30 fingerprintV0 := &proto.Fingerprint{
31 Identifier: 100,
32 GeneratedAt: timestamppb.New(time.Unix(100, 0)),
33 }
34
35 fingerprintV1 := &proto.Fingerprint{
36 Identifier: 101,
37 GeneratedAt: timestamppb.New(time.Unix(101, 0)),
38 }
39
40 fingerprintV2 := &proto.Fingerprint{
41 Identifier: 102,
42 GeneratedAt: timestamppb.New(time.Unix(102, 0)),
43 }
44
45 mkSearchConfigurationResponse := func(fingerprint *proto.Fingerprint, repoIDs ...int32) *proto.SearchConfigurationResponse {
46 repositories := make([]*proto.ZoektIndexOptions, 0, len(repoIDs))
47 for _, repoID := range repoIDs {
48 repositories = append(repositories, &proto.ZoektIndexOptions{
49 RepoId: repoID,
50 })
51 }
52
53 return &proto.SearchConfigurationResponse{
54 UpdatedOptions: repositories,
55 Fingerprint: fingerprint,
56 }
57 }
58
59 grpcClient := &mockGRPCClient{
60 mockList: func(_ context.Context, in *proto.ListRequest, opts ...grpc.CallOption) (*proto.ListResponse, error) {
61 return &proto.ListResponse{
62 RepoIds: []int32{1, 2, 3},
63 }, nil
64 },
65 }
66
67 clientOpts := []SourcegraphClientOption{
68 WithBatchSize(1),
69 }
70
71 testURL := url.URL{Scheme: "http", Host: "does.not.matter", Path: "/"}
72 sg := newSourcegraphClient(&testURL, "", grpcClient, clientOpts...)
73
74 type step struct {
75 name string
76
77 wantFingerprint *proto.Fingerprint
78 returnFingerprint *proto.Fingerprint
79 returnErr error
80 skipCheckingRepoIDs bool
81 }
82
83 for _, step := range []step{
84 {
85 name: "first call",
86 wantFingerprint: nil,
87 returnFingerprint: fingerprintV0,
88 },
89 {
90 name: "second call (should provide fingerprint from last time)",
91 wantFingerprint: fingerprintV0,
92 returnFingerprint: fingerprintV1,
93 },
94 {
95 name: "error",
96 wantFingerprint: fingerprintV1,
97 returnFingerprint: fingerprintV2,
98
99 returnErr: fmt.Errorf("boom"),
100 skipCheckingRepoIDs: true, // don't bother checking repoIDs if we expect an error
101 },
102 {
103 name: "call after error (should ignore fingerprint from last time, and provide the older one)",
104 wantFingerprint: fingerprintV1,
105 returnFingerprint: fingerprintV2,
106 },
107 } {
108 t.Run(step.name, func(t *testing.T) {
109 called := false
110 grpcClient.mockSearchConfiguration = func(_ context.Context, in *proto.SearchConfigurationRequest, opts ...grpc.CallOption) (*proto.SearchConfigurationResponse, error) {
111 called = true
112
113 diff := cmp.Diff(step.wantFingerprint, in.GetFingerprint(), protocmp.Transform())
114 if diff != "" {
115 t.Fatalf("unexpected fingerprint (-want +got):\n%s", diff)
116 }
117
118 return mkSearchConfigurationResponse(step.returnFingerprint, in.RepoIds...), step.returnErr
119 }
120
121 result, err := sg.List(context.Background(), nil)
122 if err != nil {
123 t.Fatalf("unexpected error from List: %v", err)
124 }
125
126 var iteratedIDs []uint32
127 result.IterateIndexOptions(func(options IndexOptions) {
128 iteratedIDs = append(iteratedIDs, options.RepoID)
129 })
130
131 if !called {
132 t.Fatal("expected SearchConfiguration to be called")
133 }
134
135 if step.skipCheckingRepoIDs {
136 return
137 }
138
139 sort.Slice(iteratedIDs, func(i, j int) bool {
140 return iteratedIDs[i] < iteratedIDs[j]
141 })
142
143 expectedIDs := []uint32{1, 2, 3}
144 sort.Slice(expectedIDs, func(i, j int) bool {
145 return expectedIDs[i] < expectedIDs[j]
146 })
147
148 if diff := cmp.Diff(expectedIDs, iteratedIDs); diff != "" {
149 t.Fatalf("unexpected repo ids (-want +got):\n%s", diff)
150 }
151 })
152 }
153}
154
155func TestGetIndexOptions(t *testing.T) {
156
157 type testCase struct {
158 name string
159 response *proto.SearchConfigurationResponse
160 want *IndexOptions
161 wantErr string
162 }
163
164 for _, tc := range []testCase{
165 {
166 name: "symbols, large files",
167 response: &proto.SearchConfigurationResponse{
168 UpdatedOptions: []*proto.ZoektIndexOptions{
169 {
170 Symbols: true,
171 LargeFiles: []string{"foo", "bar"},
172 },
173 },
174 },
175 want: &IndexOptions{
176 Symbols: true,
177 LargeFiles: []string{"foo", "bar"},
178 },
179 },
180 {
181 name: "no symbols , large files",
182 response: &proto.SearchConfigurationResponse{
183 UpdatedOptions: []*proto.ZoektIndexOptions{
184 {
185 Symbols: true,
186 LargeFiles: []string{"foo", "bar"},
187 },
188 },
189 },
190 want: &IndexOptions{
191 Symbols: true,
192 LargeFiles: []string{"foo", "bar"},
193 },
194 },
195
196 {
197 name: "empty",
198 response: nil,
199 want: nil,
200 },
201
202 {
203 name: "symbols",
204 response: &proto.SearchConfigurationResponse{
205 UpdatedOptions: []*proto.ZoektIndexOptions{
206 {
207 Symbols: true,
208 },
209 },
210 },
211 want: &IndexOptions{
212 Symbols: true,
213 },
214 },
215 {
216 name: "repoID",
217 response: &proto.SearchConfigurationResponse{
218 UpdatedOptions: []*proto.ZoektIndexOptions{
219 {
220 RepoId: 123,
221 },
222 },
223 },
224 want: &IndexOptions{
225 RepoID: 123,
226 },
227 },
228 {
229 name: "error",
230 response: &proto.SearchConfigurationResponse{
231 UpdatedOptions: []*proto.ZoektIndexOptions{
232 {
233 Error: "boom",
234 },
235 },
236 },
237 want: nil,
238 wantErr: "boom",
239 },
240 } {
241 called := false
242 mockClient := &mockGRPCClient{
243 mockSearchConfiguration: func(_ context.Context, _ *proto.SearchConfigurationRequest, _ ...grpc.CallOption) (*proto.SearchConfigurationResponse, error) {
244 called = true
245 return tc.response, nil
246 },
247 }
248
249 testURL := &url.URL{
250 Scheme: "http",
251 Host: "does.not.matter",
252 Path: "/",
253 }
254
255 sg := newSourcegraphClient(
256 testURL,
257 "",
258 mockClient,
259 )
260
261 var got IndexOptions
262 var err error
263 sg.ForceIterateIndexOptions(func(o IndexOptions) {
264 got = o
265 }, func(_ uint32, e error) {
266 err = e
267 }, 123)
268
269 if !called {
270 t.Fatal("expected mock to be called")
271 }
272
273 if err != nil {
274 if tc.wantErr == "" || !strings.Contains(err.Error(), tc.wantErr) {
275 t.Fatalf("unexpected error: %v", err)
276 }
277 }
278
279 if tc.want == nil {
280 continue
281 }
282
283 tc.want.CloneURL = sg.getCloneURL(got.Name)
284
285 if diff := cmp.Diff(tc.want, &got, cmpopts.EquateEmpty()); diff != "" {
286 t.Errorf("mismatch (-want +got):\n%s", diff)
287 }
288 }
289
290 // Mimic our fingerprint API, which doesn't return anything if the
291 // repo hasn't changed.
292 t.Run("unchanged", func(t *testing.T) {
293
294 called := false
295 mockClient := &mockGRPCClient{
296 mockSearchConfiguration: func(_ context.Context, _ *proto.SearchConfigurationRequest, _ ...grpc.CallOption) (*proto.SearchConfigurationResponse, error) {
297 called = true
298 return nil, nil
299 },
300 }
301
302 testURL := &url.URL{
303 Scheme: "http",
304 Host: "does.not.matter",
305 Path: "/",
306 }
307
308 sg := newSourcegraphClient(
309 testURL,
310 "",
311 mockClient,
312 )
313 gotAtLeastOneOption := false
314 var err error
315 sg.ForceIterateIndexOptions(func(_ IndexOptions) {
316 gotAtLeastOneOption = true
317 }, func(_ uint32, e error) {
318 err = e
319 }, 123)
320
321 if !called {
322 t.Fatal("expected mock to be called")
323 }
324
325 if err != nil {
326 t.Fatalf("unexpected error: %v", err)
327 }
328
329 if gotAtLeastOneOption {
330 t.Fatalf("expected no options, got %v", gotAtLeastOneOption)
331 }
332 })
333
334 var response *proto.SearchConfigurationResponse
335 mockClient := &mockGRPCClient{
336 mockSearchConfiguration: func(_ context.Context, req *proto.SearchConfigurationRequest, _ ...grpc.CallOption) (*proto.SearchConfigurationResponse, error) {
337 if len(req.GetRepoIds()) == 0 || req.GetRepoIds()[0] != 123 {
338 return nil, errors.New("invalid repo id")
339 }
340 return response, nil
341 },
342 }
343
344 sg := newSourcegraphClient(&url.URL{Path: "/"}, "", mockClient, WithBatchSize(0))
345
346 cases := []struct {
347 Response *proto.SearchConfigurationResponse
348 *IndexOptions
349 }{
350 {
351 Response: &proto.SearchConfigurationResponse{
352 UpdatedOptions: []*proto.ZoektIndexOptions{
353 {
354 Symbols: true,
355 LargeFiles: []string{"foo", "bar"},
356 },
357 },
358 },
359 IndexOptions: &IndexOptions{
360 Symbols: true,
361 LargeFiles: []string{"foo", "bar"},
362 Branches: []zoekt.RepositoryBranch{},
363 LanguageMap: map[string]ctags.CTagsParserType{},
364 },
365 },
366
367 {
368 Response: &proto.SearchConfigurationResponse{
369 UpdatedOptions: []*proto.ZoektIndexOptions{
370 {
371 Symbols: false,
372 LargeFiles: []string{"foo", "bar"},
373 },
374 },
375 },
376 IndexOptions: &IndexOptions{
377 LargeFiles: []string{"foo", "bar"},
378 Branches: []zoekt.RepositoryBranch{},
379 LanguageMap: map[string]ctags.CTagsParserType{},
380 },
381 },
382
383 {
384 Response: &proto.SearchConfigurationResponse{},
385 },
386
387 {
388 Response: &proto.SearchConfigurationResponse{
389 UpdatedOptions: []*proto.ZoektIndexOptions{
390 {
391 Symbols: true,
392 },
393 },
394 },
395 IndexOptions: &IndexOptions{
396 Symbols: true,
397 Branches: []zoekt.RepositoryBranch{},
398 LanguageMap: map[string]ctags.CTagsParserType{},
399 },
400 },
401
402 {
403 Response: &proto.SearchConfigurationResponse{
404 UpdatedOptions: []*proto.ZoektIndexOptions{
405 {
406 RepoId: 123,
407 },
408 },
409 },
410 IndexOptions: &IndexOptions{
411 RepoID: 123,
412 Branches: []zoekt.RepositoryBranch{},
413 LanguageMap: map[string]ctags.CTagsParserType{},
414 },
415 },
416
417 {
418 Response: &proto.SearchConfigurationResponse{
419 UpdatedOptions: []*proto.ZoektIndexOptions{
420 {
421 Error: "boom",
422 },
423 },
424 },
425 },
426 }
427
428 for _, tc := range cases {
429 response = tc.Response
430
431 var got IndexOptions
432 var err error
433 sg.ForceIterateIndexOptions(func(o IndexOptions) {
434 got = o
435 }, func(_ uint32, e error) {
436 err = e
437 }, 123)
438
439 if err != nil && tc.IndexOptions != nil {
440 t.Fatalf("unexpected error: %v", err)
441 }
442 if tc.IndexOptions == nil {
443 continue
444 }
445
446 tc.IndexOptions.CloneURL = sg.getCloneURL(got.Name)
447
448 if d := cmp.Diff(*tc.IndexOptions, got); d != "" {
449 t.Errorf("mismatch (-want +got):\n%s", d)
450 }
451 }
452
453 // Special case our fingerprint API which doesn't return anything if the
454 // repo hasn't changed.
455 t.Run("unchanged", func(t *testing.T) {
456 response = &proto.SearchConfigurationResponse{}
457
458 got := false
459 var err error
460 sg.ForceIterateIndexOptions(func(_ IndexOptions) {
461 got = true
462 }, func(_ uint32, e error) {
463 err = e
464 }, 123)
465 if err != nil {
466 t.Fatalf("unexpected error: %v", err)
467 }
468
469 if got {
470 t.Fatalf("expected no options, got %v", got)
471 }
472 })
473}
474
475func TestIndex(t *testing.T) {
476 cases := []struct {
477 name string
478 args indexArgs
479 mockRepositoryMetadata *zoekt.Repository
480 want []string
481 }{{
482 name: "minimal",
483 args: indexArgs{
484 IndexOptions: IndexOptions{
485 Name: "test/repo",
486 CloneURL: "http://api.test/.internal/git/test/repo",
487 Branches: []zoekt.RepositoryBranch{{Name: "HEAD", Version: "deadbeef"}},
488 },
489 },
490 want: []string{
491 "git -c init.defaultBranch=nonExistentBranchBB0FOFCH32 init --bare $TMPDIR/test%2Frepo.git",
492 "git -C $TMPDIR/test%2Frepo.git -c protocol.version=2 -c http.extraHeader=X-Sourcegraph-Actor-UID: internal fetch --depth=1 http://api.test/.internal/git/test/repo deadbeef",
493 "git -C $TMPDIR/test%2Frepo.git update-ref HEAD deadbeef",
494 "git -C $TMPDIR/test%2Frepo.git config zoekt.archived 0",
495 "git -C $TMPDIR/test%2Frepo.git config zoekt.fork 0",
496 "git -C $TMPDIR/test%2Frepo.git config zoekt.name test/repo",
497 "git -C $TMPDIR/test%2Frepo.git config zoekt.priority 0",
498 "git -C $TMPDIR/test%2Frepo.git config zoekt.public 0",
499 "git -C $TMPDIR/test%2Frepo.git config zoekt.repoid 0",
500 "zoekt-git-index -submodules=false -branches HEAD -disable_ctags $TMPDIR/test%2Frepo.git",
501 },
502 }, {
503 name: "minimal-id",
504 args: indexArgs{
505 IndexOptions: IndexOptions{
506 Name: "test/repo",
507 CloneURL: "http://api.test/.internal/git/test/repo",
508 Branches: []zoekt.RepositoryBranch{{Name: "HEAD", Version: "deadbeef"}},
509 RepoID: 123,
510 },
511 },
512 want: []string{
513 "git -c init.defaultBranch=nonExistentBranchBB0FOFCH32 init --bare $TMPDIR/test%2Frepo.git",
514 "git -C $TMPDIR/test%2Frepo.git -c protocol.version=2 -c http.extraHeader=X-Sourcegraph-Actor-UID: internal fetch --depth=1 http://api.test/.internal/git/test/repo deadbeef",
515 "git -C $TMPDIR/test%2Frepo.git update-ref HEAD deadbeef",
516 "git -C $TMPDIR/test%2Frepo.git config zoekt.archived 0",
517 "git -C $TMPDIR/test%2Frepo.git config zoekt.fork 0",
518 "git -C $TMPDIR/test%2Frepo.git config zoekt.name test/repo",
519 "git -C $TMPDIR/test%2Frepo.git config zoekt.priority 0",
520 "git -C $TMPDIR/test%2Frepo.git config zoekt.public 0",
521 "git -C $TMPDIR/test%2Frepo.git config zoekt.repoid 123",
522 "zoekt-git-index -submodules=false -branches HEAD -disable_ctags $TMPDIR/test%2Frepo.git",
523 },
524 }, {
525 name: "all",
526 args: indexArgs{
527 Incremental: true,
528 IndexDir: "/data/index",
529 Parallelism: 4,
530 FileLimit: 123,
531 IndexOptions: IndexOptions{
532 Name: "test/repo",
533 CloneURL: "http://api.test/.internal/git/test/repo",
534 LargeFiles: []string{"foo", "bar"},
535 Symbols: true,
536 Branches: []zoekt.RepositoryBranch{
537 {Name: "HEAD", Version: "deadbeef"},
538 {Name: "dev", Version: "feebdaed"}, // ignored for archive
539 },
540 },
541 },
542 want: []string{
543 "git -c init.defaultBranch=nonExistentBranchBB0FOFCH32 init --bare $TMPDIR/test%2Frepo.git",
544 "git -C $TMPDIR/test%2Frepo.git -c protocol.version=2 -c http.extraHeader=X-Sourcegraph-Actor-UID: internal fetch --depth=1 http://api.test/.internal/git/test/repo deadbeef feebdaed",
545 "git -C $TMPDIR/test%2Frepo.git update-ref HEAD deadbeef",
546 "git -C $TMPDIR/test%2Frepo.git update-ref refs/heads/dev feebdaed",
547 "git -C $TMPDIR/test%2Frepo.git config zoekt.archived 0",
548 "git -C $TMPDIR/test%2Frepo.git config zoekt.fork 0",
549 "git -C $TMPDIR/test%2Frepo.git config zoekt.name test/repo",
550 "git -C $TMPDIR/test%2Frepo.git config zoekt.priority 0",
551 "git -C $TMPDIR/test%2Frepo.git config zoekt.public 0",
552 "git -C $TMPDIR/test%2Frepo.git config zoekt.repoid 0",
553 "zoekt-git-index -submodules=false -incremental -branches HEAD,dev " +
554 "-file_limit 123 -parallelism 4 -index /data/index -require_ctags -large_file foo -large_file bar " +
555 "$TMPDIR/test%2Frepo.git",
556 },
557 }, {
558 name: "delta",
559 args: indexArgs{
560 Incremental: true,
561 IndexDir: "/data/index",
562 Parallelism: 4,
563 FileLimit: 123,
564 UseDelta: true,
565 IndexOptions: IndexOptions{
566 RepoID: 0,
567 Name: "test/repo",
568 CloneURL: "http://api.test/.internal/git/test/repo",
569 LargeFiles: []string{"foo", "bar"},
570 Symbols: true,
571 Branches: []zoekt.RepositoryBranch{
572 {Name: "HEAD", Version: "deadbeef"},
573 {Name: "dev", Version: "feebdaed"},
574 {Name: "release", Version: "12345678"},
575 },
576 },
577 DeltaShardNumberFallbackThreshold: 22,
578 },
579 mockRepositoryMetadata: &zoekt.Repository{
580 ID: 0,
581 Name: "test/repo",
582 Branches: []zoekt.RepositoryBranch{
583 {Name: "HEAD", Version: "oldhead"},
584 {Name: "dev", Version: "olddev"},
585 {Name: "release", Version: "oldrelease"},
586 },
587 },
588 want: []string{
589 "git -c init.defaultBranch=nonExistentBranchBB0FOFCH32 init --bare $TMPDIR/test%2Frepo.git",
590 "git -C $TMPDIR/test%2Frepo.git -c protocol.version=2 -c http.extraHeader=X-Sourcegraph-Actor-UID: internal fetch --depth=1 http://api.test/.internal/git/test/repo deadbeef feebdaed 12345678 oldhead olddev oldrelease",
591 "git -C $TMPDIR/test%2Frepo.git update-ref HEAD deadbeef",
592 "git -C $TMPDIR/test%2Frepo.git update-ref refs/heads/dev feebdaed",
593 "git -C $TMPDIR/test%2Frepo.git update-ref refs/heads/release 12345678",
594 "git -C $TMPDIR/test%2Frepo.git config zoekt.archived 0",
595 "git -C $TMPDIR/test%2Frepo.git config zoekt.fork 0",
596 "git -C $TMPDIR/test%2Frepo.git config zoekt.name test/repo",
597 "git -C $TMPDIR/test%2Frepo.git config zoekt.priority 0",
598 "git -C $TMPDIR/test%2Frepo.git config zoekt.public 0",
599 "git -C $TMPDIR/test%2Frepo.git config zoekt.repoid 0",
600 "zoekt-git-index -submodules=false -incremental -branches HEAD,dev,release " +
601 "-delta -delta_threshold 22 -file_limit 123 -parallelism 4 -index /data/index -require_ctags -large_file foo -large_file bar " +
602 "$TMPDIR/test%2Frepo.git",
603 },
604 }}
605
606 for _, tc := range cases {
607 t.Run(tc.name, func(t *testing.T) {
608 var got []string
609 runCmd := func(c *exec.Cmd) error {
610 cmd := strings.Join(c.Args, " ")
611 cmd = strings.ReplaceAll(cmd, filepath.Clean(os.TempDir()), "$TMPDIR")
612 got = append(got, cmd)
613 return nil
614 }
615
616 findRepositoryMetadata := func(args *indexArgs) (repository *zoekt.Repository, metadata *zoekt.IndexMetadata, ok bool, err error) {
617 if tc.mockRepositoryMetadata == nil {
618 return args.BuildOptions().FindRepositoryMetadata()
619 }
620
621 return tc.mockRepositoryMetadata, &zoekt.IndexMetadata{}, true, nil
622 }
623
624 c := gitIndexConfig{
625 runCmd: runCmd,
626 findRepositoryMetadata: findRepositoryMetadata,
627 }
628
629 if err := gitIndex(c, &tc.args, sourcegraphNop{}, logtest.Scoped(t)); err != nil {
630 t.Fatal(err)
631 }
632 if !cmp.Equal(got, tc.want) {
633 t.Errorf("git mismatch (-want +got):\n%s", cmp.Diff(tc.want, got, splitargs))
634 }
635 })
636 }
637}
638
639var splitargs = cmpopts.AcyclicTransformer("splitargs", func(cmd string) []string {
640 return strings.Split(cmd, " ")
641})
642
643type mockGRPCClient struct {
644 mockSearchConfiguration func(context.Context, *proto.SearchConfigurationRequest, ...grpc.CallOption) (*proto.SearchConfigurationResponse, error)
645 mockList func(context.Context, *proto.ListRequest, ...grpc.CallOption) (*proto.ListResponse, error)
646 mockDocumentRanks func(context.Context, *proto.DocumentRanksRequest, ...grpc.CallOption) (*proto.DocumentRanksResponse, error)
647 mockUpdateIndexStatus func(context.Context, *proto.UpdateIndexStatusRequest, ...grpc.CallOption) (*proto.UpdateIndexStatusResponse, error)
648}
649
650func (m *mockGRPCClient) SearchConfiguration(ctx context.Context, in *proto.SearchConfigurationRequest, opts ...grpc.CallOption) (*proto.SearchConfigurationResponse, error) {
651 if m.mockSearchConfiguration != nil {
652 return m.mockSearchConfiguration(ctx, in, opts...)
653 }
654
655 return nil, fmt.Errorf("mock RPC SearchConfiguration not implemented")
656}
657
658func (m *mockGRPCClient) List(ctx context.Context, in *proto.ListRequest, opts ...grpc.CallOption) (*proto.ListResponse, error) {
659 if m.mockList != nil {
660 return m.mockList(ctx, in, opts...)
661 }
662
663 return nil, fmt.Errorf("mock RPC List not implemented")
664}
665
666func (m *mockGRPCClient) DocumentRanks(ctx context.Context, in *proto.DocumentRanksRequest, opts ...grpc.CallOption) (*proto.DocumentRanksResponse, error) {
667 if m.mockDocumentRanks != nil {
668 return m.mockDocumentRanks(ctx, in, opts...)
669 }
670
671 return nil, fmt.Errorf("mock RPC DocumentRanks not implemented")
672}
673
674func (m *mockGRPCClient) UpdateIndexStatus(ctx context.Context, in *proto.UpdateIndexStatusRequest, opts ...grpc.CallOption) (*proto.UpdateIndexStatusResponse, error) {
675 if m.mockUpdateIndexStatus != nil {
676 return m.mockUpdateIndexStatus(ctx, in, opts...)
677 }
678
679 return nil, fmt.Errorf("mock RPC UpdateIndexStatus not implemented")
680}
681
682var _ proto.ZoektConfigurationServiceClient = &mockGRPCClient{}