1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
13 "git.curoverse.com/arvados.git/sdk/go/arvados"
14 "git.curoverse.com/arvados.git/sdk/go/arvadostest"
15 check "gopkg.in/check.v1"
18 var _ = check.Suite(&CollectionListSuite{})
20 type collectionLister struct {
22 ItemsToReturn []arvados.Collection
26 func (cl *collectionLister) matchFilters(c arvados.Collection, filters []arvados.Filter) bool {
28 for _, f := range filters {
29 if f.Attr == "uuid" && f.Operator == "=" {
30 s, ok := f.Operand.(string)
31 if ok && s == c.UUID {
34 } else if f.Attr == "uuid" && f.Operator == "in" {
35 if operand, ok := f.Operand.([]string); ok {
36 for _, s := range operand {
41 } else if operand, ok := f.Operand.([]interface{}); ok {
42 for _, s := range operand {
43 if s, ok := s.(string); ok && s == c.UUID {
54 func (cl *collectionLister) CollectionList(ctx context.Context, options arvados.ListOptions) (resp arvados.CollectionList, _ error) {
55 cl.APIStub.CollectionList(ctx, options)
56 for _, c := range cl.ItemsToReturn {
57 if cl.MaxPageSize > 0 && len(resp.Items) >= cl.MaxPageSize {
60 if options.Limit >= 0 && len(resp.Items) >= options.Limit {
63 if cl.matchFilters(c, options.Filters) {
64 resp.Items = append(resp.Items, c)
70 type CollectionListSuite struct {
72 ids []string // aaaaa, bbbbb, ccccc
73 uuids [][]string // [[aa-*, aa-*, aa-*], [bb-*, bb-*, ...], ...]
74 backends []*collectionLister
77 func (s *CollectionListSuite) SetUpTest(c *check.C) {
78 s.FederationSuite.SetUpTest(c)
83 for i, id := range []string{"aaaaa", "bbbbb", "ccccc"} {
84 cl := &collectionLister{}
85 s.ids = append(s.ids, id)
86 s.uuids = append(s.uuids, nil)
87 for j := 0; j < 5; j++ {
88 uuid := fmt.Sprintf("%s-4zz18-%s%010d", id, id, j)
89 s.uuids[i] = append(s.uuids[i], uuid)
90 cl.ItemsToReturn = append(cl.ItemsToReturn, arvados.Collection{
94 s.backends = append(s.backends, cl)
98 // call some backends directly via API
99 s.addDirectRemote(c, id, cl)
101 // call some backends through rpc->router->API
102 // to ensure nothing is lost in translation
103 s.addHTTPRemote(c, id, cl)
108 type listTrial struct {
113 filters []arvados.Filter
115 expectCalls []int // number of API calls to backends
119 func (s *CollectionListSuite) TestCollectionListNoUUIDFilters(c *check.C) {
123 expectUUIDs: []string{s.uuids[0][0]},
124 expectCalls: []int{1, 0, 0},
128 func (s *CollectionListSuite) TestCollectionListOneLocal(c *check.C) {
132 filters: []arvados.Filter{{"uuid", "=", s.uuids[0][0]}},
133 expectUUIDs: []string{s.uuids[0][0]},
134 expectCalls: []int{1, 0, 0},
138 func (s *CollectionListSuite) TestCollectionListOneRemote(c *check.C) {
142 filters: []arvados.Filter{{"uuid", "=", s.uuids[1][0]}},
143 expectUUIDs: []string{s.uuids[1][0]},
144 expectCalls: []int{0, 1, 0},
148 func (s *CollectionListSuite) TestCollectionListOneLocalUsingInOperator(c *check.C) {
152 filters: []arvados.Filter{{"uuid", "in", []string{s.uuids[0][0]}}},
153 expectUUIDs: []string{s.uuids[0][0]},
154 expectCalls: []int{1, 0, 0},
158 func (s *CollectionListSuite) TestCollectionListOneRemoteUsingInOperator(c *check.C) {
162 filters: []arvados.Filter{{"uuid", "in", []string{s.uuids[1][1]}}},
163 expectUUIDs: []string{s.uuids[1][1]},
164 expectCalls: []int{0, 1, 0},
168 func (s *CollectionListSuite) TestCollectionListOneLocalOneRemote(c *check.C) {
172 filters: []arvados.Filter{{"uuid", "in", []string{s.uuids[0][0], s.uuids[1][0]}}},
173 expectUUIDs: []string{s.uuids[0][0], s.uuids[1][0]},
174 expectCalls: []int{1, 1, 0},
178 func (s *CollectionListSuite) TestCollectionListTwoRemotes(c *check.C) {
182 filters: []arvados.Filter{{"uuid", "in", []string{s.uuids[2][0], s.uuids[1][0]}}},
183 expectUUIDs: []string{s.uuids[1][0], s.uuids[2][0]},
184 expectCalls: []int{0, 1, 1},
188 func (s *CollectionListSuite) TestCollectionListSatisfyAllFilters(c *check.C) {
189 s.cluster.API.MaxItemsPerResponse = 2
193 filters: []arvados.Filter{
194 {"uuid", "in", []string{s.uuids[0][0], s.uuids[1][1], s.uuids[2][0], s.uuids[2][1], s.uuids[2][2]}},
195 {"uuid", "in", []string{s.uuids[0][0], s.uuids[1][2], s.uuids[2][1]}},
197 expectUUIDs: []string{s.uuids[0][0], s.uuids[2][1]},
198 expectCalls: []int{1, 0, 1},
202 func (s *CollectionListSuite) TestCollectionListEmptySet(c *check.C) {
206 filters: []arvados.Filter{{"uuid", "in", []string{}}},
207 expectUUIDs: []string{},
208 expectCalls: []int{0, 0, 0},
212 func (s *CollectionListSuite) TestCollectionListUnmatchableUUID(c *check.C) {
216 filters: []arvados.Filter{
217 {"uuid", "in", []string{s.uuids[0][0], "abcdefg"}},
218 {"uuid", "in", []string{s.uuids[0][0], "bbbbb-4zz18-bogus"}},
219 {"uuid", "in", []string{s.uuids[0][0], "bogus-4zz18-bogus"}},
221 expectUUIDs: []string{s.uuids[0][0]},
222 expectCalls: []int{1, 0, 0},
226 func (s *CollectionListSuite) TestCollectionListMultiPage(c *check.C) {
227 for i := range s.backends {
228 s.uuids[i] = s.uuids[i][:3]
229 s.backends[i].ItemsToReturn = s.backends[i].ItemsToReturn[:3]
231 s.cluster.API.MaxItemsPerResponse = 9
232 for _, stub := range s.backends {
235 allUUIDs := append(append(append([]string(nil), s.uuids[0]...), s.uuids[1]...), s.uuids[2]...)
239 filters: []arvados.Filter{{"uuid", "in", append([]string(nil), allUUIDs...)}},
240 expectUUIDs: allUUIDs,
241 expectCalls: []int{2, 2, 2},
245 func (s *CollectionListSuite) TestCollectionListMultiSiteExtraFilters(c *check.C) {
246 // not [yet] supported
250 filters: []arvados.Filter{
251 {"uuid", "in", []string{s.uuids[0][0], s.uuids[1][0]}},
252 {"uuid", "is_a", "teapot"},
254 expectCalls: []int{0, 0, 0},
255 expectStatus: http.StatusBadRequest,
259 func (s *CollectionListSuite) TestCollectionListMultiSiteWithCount(c *check.C) {
260 for _, count := range []string{"", "exact"} {
264 filters: []arvados.Filter{
265 {"uuid", "in", []string{s.uuids[0][0], s.uuids[1][0]}},
266 {"uuid", "is_a", "teapot"},
268 expectCalls: []int{0, 0, 0},
269 expectStatus: http.StatusBadRequest,
274 func (s *CollectionListSuite) TestCollectionListMultiSiteWithLimit(c *check.C) {
275 for _, limit := range []int{0, 1, 2} {
279 filters: []arvados.Filter{
280 {"uuid", "in", []string{s.uuids[0][0], s.uuids[1][0]}},
281 {"uuid", "is_a", "teapot"},
283 expectCalls: []int{0, 0, 0},
284 expectStatus: http.StatusBadRequest,
289 func (s *CollectionListSuite) TestCollectionListMultiSiteWithOffset(c *check.C) {
294 filters: []arvados.Filter{
295 {"uuid", "in", []string{s.uuids[0][0], s.uuids[1][0]}},
296 {"uuid", "is_a", "teapot"},
298 expectCalls: []int{0, 0, 0},
299 expectStatus: http.StatusBadRequest,
303 func (s *CollectionListSuite) TestCollectionListMultiSiteWithOrder(c *check.C) {
307 order: []string{"uuid desc"},
308 filters: []arvados.Filter{
309 {"uuid", "in", []string{s.uuids[0][0], s.uuids[1][0]}},
310 {"uuid", "is_a", "teapot"},
312 expectCalls: []int{0, 0, 0},
313 expectStatus: http.StatusBadRequest,
317 func (s *CollectionListSuite) TestCollectionListInvalidFilters(c *check.C) {
321 filters: []arvados.Filter{
322 {"uuid", "in", "teapot"},
324 expectCalls: []int{0, 0, 0},
325 expectStatus: http.StatusBadRequest,
329 func (s *CollectionListSuite) TestCollectionListRemoteUnknown(c *check.C) {
333 filters: []arvados.Filter{
334 {"uuid", "in", []string{s.uuids[0][0], "bogus-4zz18-000001111122222"}},
336 expectStatus: http.StatusNotFound,
340 func (s *CollectionListSuite) TestCollectionListRemoteError(c *check.C) {
341 s.addDirectRemote(c, "bbbbb", &arvadostest.APIStub{})
345 filters: []arvados.Filter{
346 {"uuid", "in", []string{s.uuids[0][0], s.uuids[1][0]}},
348 expectStatus: http.StatusBadGateway,
352 func (s *CollectionListSuite) test(c *check.C, trial listTrial) {
353 resp, err := s.fed.CollectionList(s.ctx, arvados.ListOptions{
356 Offset: trial.offset,
358 Filters: trial.filters,
360 if trial.expectStatus != 0 {
361 c.Assert(err, check.NotNil)
362 err, _ := err.(interface{ HTTPStatus() int })
363 c.Assert(err, check.NotNil) // err must implement HTTPStatus()
364 c.Check(err.HTTPStatus(), check.Equals, trial.expectStatus)
365 c.Logf("returned error is %#v", err)
366 c.Logf("returned error string is %q", err)
368 c.Check(err, check.IsNil)
369 expectItems := []arvados.Collection{}
370 for _, uuid := range trial.expectUUIDs {
371 expectItems = append(expectItems, arvados.Collection{UUID: uuid})
373 // expectItems is sorted by UUID, so sort resp.Items
374 // by UUID before checking DeepEquals.
375 sort.Slice(resp.Items, func(i, j int) bool { return resp.Items[i].UUID < resp.Items[j].UUID })
376 c.Check(resp, check.DeepEquals, arvados.CollectionList{
381 for i, stub := range s.backends {
382 if i >= len(trial.expectCalls) {
385 calls := stub.Calls(nil)
386 c.Check(calls, check.HasLen, trial.expectCalls[i])
390 opts := calls[0].Options.(arvados.ListOptions)
391 c.Check(opts.Limit, check.Equals, trial.limit)