/cmd/frontend/graphqlbackend/search_repositories_test.go

https://github.com/sourcegraph/sourcegraph · Go · 223 lines · 203 code · 18 blank · 2 comment · 27 complexity · f662ae5da2ce9be955752dc352149dda MD5 · raw file

  1. package graphqlbackend
  2. import (
  3. "context"
  4. "errors"
  5. "sort"
  6. "testing"
  7. "github.com/google/go-cmp/cmp"
  8. "github.com/google/go-cmp/cmp/cmpopts"
  9. "github.com/sourcegraph/sourcegraph/cmd/frontend/types"
  10. "github.com/sourcegraph/sourcegraph/internal/search"
  11. searchbackend "github.com/sourcegraph/sourcegraph/internal/search/backend"
  12. "github.com/sourcegraph/sourcegraph/internal/search/query"
  13. )
  14. func TestSearchRepositories(t *testing.T) {
  15. repositories := []*search.RepositoryRevisions{
  16. {Repo: &types.Repo{ID: 123, Name: "foo/one"}, Revs: []search.RevisionSpecifier{{RevSpec: ""}}},
  17. {Repo: &types.Repo{ID: 456, Name: "foo/no-match"}, Revs: []search.RevisionSpecifier{{RevSpec: ""}}},
  18. {Repo: &types.Repo{ID: 789, Name: "bar/one"}, Revs: []search.RevisionSpecifier{{RevSpec: ""}}},
  19. }
  20. zoekt := &searchbackend.Zoekt{Client: &fakeSearcher{}}
  21. mockSearchFilesInRepos = func(args *search.TextParameters) (matches []*FileMatchResolver, common *searchResultsCommon, err error) {
  22. repoName := args.Repos[0].Repo.Name
  23. switch repoName {
  24. case "foo/one":
  25. return []*FileMatchResolver{
  26. {
  27. uri: "git://" + string(repoName) + "?1a2b3c#" + "f.go",
  28. Repo: &RepositoryResolver{repo: &types.Repo{ID: 123}},
  29. },
  30. }, &searchResultsCommon{}, nil
  31. case "bar/one":
  32. return []*FileMatchResolver{
  33. {
  34. uri: "git://" + string(repoName) + "?1a2b3c#" + "f.go",
  35. Repo: &RepositoryResolver{repo: &types.Repo{ID: 789}},
  36. },
  37. }, &searchResultsCommon{}, nil
  38. case "foo/no-match":
  39. return []*FileMatchResolver{}, &searchResultsCommon{}, nil
  40. default:
  41. return nil, &searchResultsCommon{}, errors.New("Unexpected repo")
  42. }
  43. }
  44. cases := []struct {
  45. name string
  46. q string
  47. want []string
  48. }{{
  49. name: "all",
  50. q: "type:repo",
  51. want: []string{"bar/one", "foo/no-match", "foo/one"},
  52. }, {
  53. name: "pattern filter",
  54. q: "type:repo foo/one",
  55. want: []string{"foo/one"},
  56. }, {
  57. name: "repohasfile",
  58. q: "foo type:repo repohasfile:f.go",
  59. want: []string{"foo/one"},
  60. }, {
  61. name: "case yes match",
  62. q: "foo case:yes",
  63. want: []string{"foo/no-match", "foo/one"},
  64. }, {
  65. name: "case no match",
  66. q: "Foo case:no",
  67. want: []string{"foo/no-match", "foo/one"},
  68. }, {
  69. name: "case exclude all",
  70. q: "Foo case:yes",
  71. want: []string{},
  72. }}
  73. for _, tc := range cases {
  74. t.Run(tc.name, func(t *testing.T) {
  75. q, err := query.ParseAndCheck(tc.q)
  76. if err != nil {
  77. t.Fatal(err)
  78. }
  79. pattern, err := getPatternInfo(q, &getPatternInfoOptions{fileMatchLimit: 1})
  80. if err != nil {
  81. t.Fatal(err)
  82. }
  83. results, _, err := searchRepositories(context.Background(), &search.TextParameters{
  84. PatternInfo: pattern,
  85. Repos: repositories,
  86. Query: q,
  87. Zoekt: zoekt,
  88. }, int32(100))
  89. if err != nil {
  90. t.Fatal(err)
  91. }
  92. var got []string
  93. for _, res := range results {
  94. r, ok := res.ToRepository()
  95. if !ok {
  96. t.Fatal("expected repo result")
  97. }
  98. got = append(got, string(r.repo.Name))
  99. }
  100. sort.Strings(got)
  101. if !cmp.Equal(tc.want, got, cmpopts.EquateEmpty()) {
  102. t.Errorf("mismatch (-want +got):\n%s", cmp.Diff(tc.want, got))
  103. }
  104. })
  105. }
  106. }
  107. func TestRepoShouldBeAdded(t *testing.T) {
  108. mockSearchFilesInRepos = func(args *search.TextParameters) (matches []*FileMatchResolver, common *searchResultsCommon, err error) {
  109. repoName := args.Repos[0].Repo.Name
  110. switch repoName {
  111. case "foo/one":
  112. return []*FileMatchResolver{
  113. {
  114. uri: "git://" + string(repoName) + "?1a2b3c#" + "foo.go",
  115. Repo: &RepositoryResolver{repo: &types.Repo{ID: 123}},
  116. },
  117. }, &searchResultsCommon{}, nil
  118. case "foo/no-match":
  119. return []*FileMatchResolver{}, &searchResultsCommon{}, nil
  120. default:
  121. return nil, &searchResultsCommon{}, errors.New("Unexpected repo")
  122. }
  123. }
  124. zoekt := &searchbackend.Zoekt{Client: &fakeSearcher{}}
  125. t.Run("repo should be included in results, query has repoHasFile filter", func(t *testing.T) {
  126. repo := &search.RepositoryRevisions{Repo: &types.Repo{ID: 123, Name: "foo/one"}, Revs: []search.RevisionSpecifier{{RevSpec: ""}}}
  127. mockSearchFilesInRepos = func(args *search.TextParameters) (matches []*FileMatchResolver, common *searchResultsCommon, err error) {
  128. return []*FileMatchResolver{
  129. {
  130. uri: "git://" + string(repo.Repo.Name) + "?1a2b3c#" + "foo.go",
  131. Repo: &RepositoryResolver{repo: &types.Repo{ID: 123}},
  132. },
  133. }, &searchResultsCommon{}, nil
  134. }
  135. pat := &search.TextPatternInfo{Pattern: "", FilePatternsReposMustInclude: []string{"foo"}, IsRegExp: true, FileMatchLimit: 1, PathPatternsAreCaseSensitive: false, PatternMatchesContent: true, PatternMatchesPath: true}
  136. shouldBeAdded, err := repoShouldBeAdded(context.Background(), zoekt, repo, pat)
  137. if err != nil {
  138. t.Fatal(err)
  139. }
  140. if !shouldBeAdded {
  141. t.Errorf("Expected shouldBeAdded for repo %v to be true, but got false", repo)
  142. }
  143. })
  144. t.Run("repo shouldn't be included in results, query has repoHasFile filter ", func(t *testing.T) {
  145. repo := &search.RepositoryRevisions{Repo: &types.Repo{Name: "foo/no-match"}, Revs: []search.RevisionSpecifier{{RevSpec: ""}}}
  146. mockSearchFilesInRepos = func(args *search.TextParameters) (matches []*FileMatchResolver, common *searchResultsCommon, err error) {
  147. return []*FileMatchResolver{}, &searchResultsCommon{}, nil
  148. }
  149. pat := &search.TextPatternInfo{Pattern: "", FilePatternsReposMustInclude: []string{"foo"}, IsRegExp: true, FileMatchLimit: 1, PathPatternsAreCaseSensitive: false, PatternMatchesContent: true, PatternMatchesPath: true}
  150. shouldBeAdded, err := repoShouldBeAdded(context.Background(), zoekt, repo, pat)
  151. if err != nil {
  152. t.Fatal(err)
  153. }
  154. if shouldBeAdded {
  155. t.Errorf("Expected shouldBeAdded for repo %v to be false, but got true", repo)
  156. }
  157. })
  158. t.Run("repo shouldn't be included in results, query has -repoHasFile filter", func(t *testing.T) {
  159. repo := &search.RepositoryRevisions{Repo: &types.Repo{ID: 123, Name: "foo/one"}, Revs: []search.RevisionSpecifier{{RevSpec: ""}}}
  160. mockSearchFilesInRepos = func(args *search.TextParameters) (matches []*FileMatchResolver, common *searchResultsCommon, err error) {
  161. return []*FileMatchResolver{
  162. {
  163. uri: "git://" + string(repo.Repo.Name) + "?1a2b3c#" + "foo.go",
  164. Repo: &RepositoryResolver{repo: &types.Repo{ID: 123}},
  165. },
  166. }, &searchResultsCommon{}, nil
  167. }
  168. pat := &search.TextPatternInfo{Pattern: "", FilePatternsReposMustExclude: []string{"foo"}, IsRegExp: true, FileMatchLimit: 1, PathPatternsAreCaseSensitive: false, PatternMatchesContent: true, PatternMatchesPath: true}
  169. shouldBeAdded, err := repoShouldBeAdded(context.Background(), zoekt, repo, pat)
  170. if err != nil {
  171. t.Fatal(err)
  172. }
  173. if shouldBeAdded {
  174. t.Errorf("Expected shouldBeAdded for repo %v to be false, but got true", repo)
  175. }
  176. })
  177. t.Run("repo should be included in results, query has -repoHasFile filter", func(t *testing.T) {
  178. repo := &search.RepositoryRevisions{Repo: &types.Repo{Name: "foo/no-match"}, Revs: []search.RevisionSpecifier{{RevSpec: ""}}}
  179. mockSearchFilesInRepos = func(args *search.TextParameters) (matches []*FileMatchResolver, common *searchResultsCommon, err error) {
  180. return []*FileMatchResolver{}, &searchResultsCommon{}, nil
  181. }
  182. pat := &search.TextPatternInfo{Pattern: "", FilePatternsReposMustExclude: []string{"foo"}, IsRegExp: true, FileMatchLimit: 1, PathPatternsAreCaseSensitive: false, PatternMatchesContent: true, PatternMatchesPath: true}
  183. shouldBeAdded, err := repoShouldBeAdded(context.Background(), zoekt, repo, pat)
  184. if err != nil {
  185. t.Fatal(err)
  186. }
  187. if !shouldBeAdded {
  188. t.Errorf("Expected shouldBeAdded for repo %v to be true, but got false", repo)
  189. }
  190. })
  191. }
  192. // repoShouldBeAdded determines whether a repository should be included in the result set based on whether the repository fits in the subset
  193. // of repostiories specified in the query's `repohasfile` and `-repohasfile` fields if they exist.
  194. func repoShouldBeAdded(ctx context.Context, zoekt *searchbackend.Zoekt, repo *search.RepositoryRevisions, pattern *search.TextPatternInfo) (bool, error) {
  195. repos := []*search.RepositoryRevisions{repo}
  196. args := search.TextParameters{
  197. PatternInfo: pattern,
  198. Zoekt: zoekt,
  199. }
  200. rsta, err := reposToAdd(ctx, &args, repos)
  201. if err != nil {
  202. return false, err
  203. }
  204. return len(rsta) == 1, nil
  205. }