1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
14 "git.arvados.org/arvados.git/sdk/go/arvados"
15 "git.arvados.org/arvados.git/sdk/go/arvadostest"
19 // Gocheck boilerplate
20 func Test(t *testing.T) {
24 type TestSuite struct {
26 users map[string]arvados.User
29 func (s *TestSuite) SetUpTest(c *C) {
30 ac := arvados.NewClientFromEnv()
31 u, err := ac.CurrentUser()
33 // Check that the parent group doesn't exist
34 sysUserUUID := u.UUID[:12] + "000000000000000"
35 gl := arvados.GroupList{}
36 params := arvados.ResourceListParams{
37 Filters: []arvados.Filter{{
44 Operand: "Externally synchronized groups",
47 ac.RequestAndDecode(&gl, "GET", "/arvados/v1/groups", nil, params)
48 c.Assert(gl.ItemsAvailable, Equals, 0)
50 os.Args = []string{"cmd", "somefile.csv"}
51 config, err := GetConfig()
53 // Confirm that the parent group was created
54 gl = arvados.GroupList{}
55 ac.RequestAndDecode(&gl, "GET", "/arvados/v1/groups", nil, params)
56 c.Assert(gl.ItemsAvailable, Equals, 1)
57 // Config set up complete, save config for further testing
60 // Fetch current user list
61 ul := arvados.UserList{}
62 params = arvados.ResourceListParams{
63 Filters: []arvados.Filter{{
66 Operand: s.cfg.SysUserUUID,
69 ac.RequestAndDecode(&ul, "GET", "/arvados/v1/users", nil, params)
70 c.Assert(ul.ItemsAvailable, Not(Equals), 0)
71 s.users = make(map[string]arvados.User)
72 for _, u := range ul.Items {
75 c.Assert(len(s.users), Not(Equals), 0)
78 func (s *TestSuite) TearDownTest(c *C) {
80 // Reset database to fixture state after every test run.
81 err := s.cfg.Client.RequestAndDecode(&dst, "POST", "/database/reset", nil, nil)
85 var _ = Suite(&TestSuite{})
87 // MakeTempCSVFile creates a temp file with data as comma separated values
88 func MakeTempCSVFile(data [][]string) (f *os.File, err error) {
89 f, err = ioutil.TempFile("", "test_sync_remote_groups")
93 for _, line := range data {
94 fmt.Fprintf(f, "%s\n", strings.Join(line, ","))
100 // GroupMembershipExists checks that both needed links exist between user and group
101 func GroupMembershipExists(ac *arvados.Client, userUUID string, groupUUID string, perm string) bool {
103 // Check Group -> User can_read permission
104 params := arvados.ResourceListParams{
105 Filters: []arvados.Filter{{
108 Operand: "permission",
123 ac.RequestAndDecode(&ll, "GET", "/arvados/v1/links", nil, params)
127 // Check User -> Group can_write permission
128 params = arvados.ResourceListParams{
129 Filters: []arvados.Filter{{
132 Operand: "permission",
147 ac.RequestAndDecode(&ll, "GET", "/arvados/v1/links", nil, params)
154 // If named group exists, return its UUID
155 func RemoteGroupExists(cfg *ConfigParams, groupName string) (uuid string, err error) {
156 gl := arvados.GroupList{}
157 params := arvados.ResourceListParams{
158 Filters: []arvados.Filter{{
165 Operand: cfg.SysUserUUID,
172 err = cfg.Client.RequestAndDecode(&gl, "GET", "/arvados/v1/groups", nil, params)
176 if gl.ItemsAvailable == 0 {
177 // No group with this name
179 } else if gl.ItemsAvailable == 1 {
181 uuid = gl.Items[0].UUID
183 // This should never happen
185 err = fmt.Errorf("more than 1 group found with the same name and parent")
190 func (s *TestSuite) TestParseFlagsWithPositionalArgument(c *C) {
191 cfg := ConfigParams{}
192 os.Args = []string{"cmd", "-verbose", "/tmp/somefile.csv"}
193 err := ParseFlags(&cfg)
195 c.Check(cfg.Path, Equals, "/tmp/somefile.csv")
196 c.Check(cfg.Verbose, Equals, true)
199 func (s *TestSuite) TestParseFlagsWithoutPositionalArgument(c *C) {
200 os.Args = []string{"cmd", "-verbose"}
201 err := ParseFlags(&ConfigParams{})
202 c.Assert(err, NotNil)
205 func (s *TestSuite) TestGetUserID(c *C) {
207 Email: "testuser@example.com",
208 Username: "Testuser",
210 email, err := GetUserID(u, "email")
212 c.Check(email, Equals, "testuser@example.com")
213 _, err = GetUserID(u, "bogus")
214 c.Assert(err, NotNil)
217 func (s *TestSuite) TestGetConfig(c *C) {
218 os.Args = []string{"cmd", "/tmp/somefile.csv"}
219 cfg, err := GetConfig()
221 c.Check(cfg.SysUserUUID, NotNil)
222 c.Check(cfg.Client, NotNil)
223 c.Check(cfg.ParentGroupUUID, NotNil)
224 c.Check(cfg.ParentGroupName, Equals, "Externally synchronized groups")
227 // Ignore leading & trailing spaces on group & users names
228 func (s *TestSuite) TestIgnoreSpaces(c *C) {
229 activeUserEmail := s.users[arvadostest.ActiveUserUUID].Email
230 activeUserUUID := s.users[arvadostest.ActiveUserUUID].UUID
231 // Confirm that the groups don't exist
232 for _, groupName := range []string{"TestGroup1", "TestGroup2", "Test Group 3"} {
233 groupUUID, err := RemoteGroupExists(s.cfg, groupName)
235 c.Assert(groupUUID, Equals, "")
238 {" TestGroup1", activeUserEmail},
239 {"TestGroup2 ", " " + activeUserEmail},
240 {" Test Group 3 ", activeUserEmail + " "},
242 tmpfile, err := MakeTempCSVFile(data)
244 defer os.Remove(tmpfile.Name()) // clean up
245 s.cfg.Path = tmpfile.Name()
248 // Check that 3 groups were created correctly, and have the active user as
250 for _, groupName := range []string{"TestGroup1", "TestGroup2", "Test Group 3"} {
251 groupUUID, err := RemoteGroupExists(s.cfg, groupName)
253 c.Assert(groupUUID, Not(Equals), "")
254 c.Assert(GroupMembershipExists(s.cfg.Client, activeUserUUID, groupUUID, "can_write"), Equals, true)
258 // Error out when records have <2 or >3 records
259 func (s *TestSuite) TestWrongNumberOfFields(c *C) {
260 for _, testCase := range [][][]string{
262 {{"field1", "field2", "field3", "field4"}},
263 {{"field1", "field2", "field3", "field4", "field5"}},
265 tmpfile, err := MakeTempCSVFile(testCase)
267 defer os.Remove(tmpfile.Name())
268 s.cfg.Path = tmpfile.Name()
270 c.Assert(err, Not(IsNil))
274 // Check different membership permissions
275 func (s *TestSuite) TestMembershipLevels(c *C) {
276 userEmail := s.users[arvadostest.ActiveUserUUID].Email
277 userUUID := s.users[arvadostest.ActiveUserUUID].UUID
279 {"TestGroup1", userEmail, "can_read"},
280 {"TestGroup2", userEmail, "can_write"},
281 {"TestGroup3", userEmail, "can_manage"},
282 {"TestGroup4", userEmail, "invalid_permission"},
284 tmpfile, err := MakeTempCSVFile(data)
286 defer os.Remove(tmpfile.Name()) // clean up
287 s.cfg.Path = tmpfile.Name()
290 for _, record := range data {
291 groupName := record[0]
292 permLevel := record[2]
293 if permLevel != "invalid_permission" {
294 groupUUID, err := RemoteGroupExists(s.cfg, groupName)
296 c.Assert(groupUUID, Not(Equals), "")
297 c.Assert(GroupMembershipExists(s.cfg.Client, userUUID, groupUUID, permLevel), Equals, true)
299 groupUUID, err := RemoteGroupExists(s.cfg, groupName)
301 c.Assert(groupUUID, Equals, "")
306 // Check membership level change
307 func (s *TestSuite) TestMembershipLevelUpdate(c *C) {
308 userEmail := s.users[arvadostest.ActiveUserUUID].Email
309 userUUID := s.users[arvadostest.ActiveUserUUID].UUID
310 groupName := "TestGroup1"
311 // Give read permissions
312 tmpfile, err := MakeTempCSVFile([][]string{{groupName, userEmail, "can_read"}})
314 defer os.Remove(tmpfile.Name()) // clean up
315 s.cfg.Path = tmpfile.Name()
319 groupUUID, err := RemoteGroupExists(s.cfg, groupName)
321 c.Assert(groupUUID, Not(Equals), "")
322 c.Assert(GroupMembershipExists(s.cfg.Client, userUUID, groupUUID, "can_read"), Equals, true)
323 c.Assert(GroupMembershipExists(s.cfg.Client, userUUID, groupUUID, "can_write"), Equals, false)
324 c.Assert(GroupMembershipExists(s.cfg.Client, userUUID, groupUUID, "can_manage"), Equals, false)
326 // Give write permissions
327 tmpfile, err = MakeTempCSVFile([][]string{{groupName, userEmail, "can_write"}})
329 defer os.Remove(tmpfile.Name()) // clean up
330 s.cfg.Path = tmpfile.Name()
334 c.Assert(GroupMembershipExists(s.cfg.Client, userUUID, groupUUID, "can_read"), Equals, false)
335 c.Assert(GroupMembershipExists(s.cfg.Client, userUUID, groupUUID, "can_write"), Equals, true)
336 c.Assert(GroupMembershipExists(s.cfg.Client, userUUID, groupUUID, "can_manage"), Equals, false)
338 // Give manage permissions
339 tmpfile, err = MakeTempCSVFile([][]string{{groupName, userEmail, "can_manage"}})
341 defer os.Remove(tmpfile.Name()) // clean up
342 s.cfg.Path = tmpfile.Name()
346 c.Assert(GroupMembershipExists(s.cfg.Client, userUUID, groupUUID, "can_read"), Equals, false)
347 c.Assert(GroupMembershipExists(s.cfg.Client, userUUID, groupUUID, "can_write"), Equals, false)
348 c.Assert(GroupMembershipExists(s.cfg.Client, userUUID, groupUUID, "can_manage"), Equals, true)
351 // The absence of a user membership on the CSV file implies its removal
352 func (s *TestSuite) TestMembershipRemoval(c *C) {
353 localUserEmail := s.users[arvadostest.ActiveUserUUID].Email
354 localUserUUID := s.users[arvadostest.ActiveUserUUID].UUID
355 remoteUserEmail := s.users[arvadostest.FederatedActiveUserUUID].Email
356 remoteUserUUID := s.users[arvadostest.FederatedActiveUserUUID].UUID
358 {"TestGroup1", localUserEmail},
359 {"TestGroup1", remoteUserEmail},
360 {"TestGroup2", localUserEmail},
361 {"TestGroup2", remoteUserEmail},
363 tmpfile, err := MakeTempCSVFile(data)
365 defer os.Remove(tmpfile.Name()) // clean up
366 s.cfg.Path = tmpfile.Name()
369 // Confirm that memberships exist
370 for _, groupName := range []string{"TestGroup1", "TestGroup2"} {
371 groupUUID, err := RemoteGroupExists(s.cfg, groupName)
373 c.Assert(groupUUID, Not(Equals), "")
374 c.Assert(GroupMembershipExists(s.cfg.Client, localUserUUID, groupUUID, "can_write"), Equals, true)
375 c.Assert(GroupMembershipExists(s.cfg.Client, remoteUserUUID, groupUUID, "can_write"), Equals, true)
377 // New CSV with some previous membership missing
379 {"TestGroup1", localUserEmail},
380 {"TestGroup2", remoteUserEmail},
382 tmpfile2, err := MakeTempCSVFile(data)
384 defer os.Remove(tmpfile2.Name()) // clean up
385 s.cfg.Path = tmpfile2.Name()
388 // Confirm TestGroup1 memberships
389 groupUUID, err := RemoteGroupExists(s.cfg, "TestGroup1")
391 c.Assert(groupUUID, Not(Equals), "")
392 c.Assert(GroupMembershipExists(s.cfg.Client, localUserUUID, groupUUID, "can_write"), Equals, true)
393 c.Assert(GroupMembershipExists(s.cfg.Client, remoteUserUUID, groupUUID, "can_write"), Equals, false)
394 // Confirm TestGroup1 memberships
395 groupUUID, err = RemoteGroupExists(s.cfg, "TestGroup2")
397 c.Assert(groupUUID, Not(Equals), "")
398 c.Assert(GroupMembershipExists(s.cfg.Client, localUserUUID, groupUUID, "can_write"), Equals, false)
399 c.Assert(GroupMembershipExists(s.cfg.Client, remoteUserUUID, groupUUID, "can_write"), Equals, true)
402 // If a group doesn't exist on the system, create it before adding users
403 func (s *TestSuite) TestAutoCreateGroupWhenNotExisting(c *C) {
404 groupName := "Testers"
405 // Confirm that group doesn't exist
406 groupUUID, err := RemoteGroupExists(s.cfg, groupName)
408 c.Assert(groupUUID, Equals, "")
409 // Make a tmp CSV file
411 {groupName, s.users[arvadostest.ActiveUserUUID].Email},
413 tmpfile, err := MakeTempCSVFile(data)
415 defer os.Remove(tmpfile.Name()) // clean up
416 s.cfg.Path = tmpfile.Name()
419 // "Testers" group should now exist
420 groupUUID, err = RemoteGroupExists(s.cfg, groupName)
422 c.Assert(groupUUID, Not(Equals), "")
423 // active user should be a member
424 c.Assert(GroupMembershipExists(s.cfg.Client, arvadostest.ActiveUserUUID, groupUUID, "can_write"), Equals, true)
427 // Users listed on the file that don't exist on the system are ignored
428 func (s *TestSuite) TestIgnoreNonexistantUsers(c *C) {
429 activeUserEmail := s.users[arvadostest.ActiveUserUUID].Email
430 activeUserUUID := s.users[arvadostest.ActiveUserUUID].UUID
431 // Confirm that group doesn't exist
432 groupUUID, err := RemoteGroupExists(s.cfg, "TestGroup4")
434 c.Assert(groupUUID, Equals, "")
435 // Create file & run command
437 {"TestGroup4", "nonexistantuser@unknowndomain.com"}, // Processed first
438 {"TestGroup4", activeUserEmail},
440 tmpfile, err := MakeTempCSVFile(data)
442 defer os.Remove(tmpfile.Name()) // clean up
443 s.cfg.Path = tmpfile.Name()
446 // Confirm that memberships exist
447 groupUUID, err = RemoteGroupExists(s.cfg, "TestGroup4")
449 c.Assert(groupUUID, Not(Equals), "")
450 c.Assert(GroupMembershipExists(s.cfg.Client, activeUserUUID, groupUUID, "can_write"), Equals, true)
453 // Users listed on the file that don't exist on the system are ignored
454 func (s *TestSuite) TestIgnoreEmptyFields(c *C) {
455 activeUserEmail := s.users[arvadostest.ActiveUserUUID].Email
456 activeUserUUID := s.users[arvadostest.ActiveUserUUID].UUID
457 // Confirm that group doesn't exist
458 for _, groupName := range []string{"TestGroup4", "TestGroup5"} {
459 groupUUID, err := RemoteGroupExists(s.cfg, groupName)
461 c.Assert(groupUUID, Equals, "")
463 // Create file & run command
465 {"", activeUserEmail}, // Empty field
466 {"TestGroup5", ""}, // Empty field
467 {"TestGroup5", activeUserEmail, ""}, // Empty 3rd field: is optional but cannot be empty
468 {"TestGroup4", activeUserEmail},
470 tmpfile, err := MakeTempCSVFile(data)
472 defer os.Remove(tmpfile.Name()) // clean up
473 s.cfg.Path = tmpfile.Name()
476 // Confirm that records about TestGroup5 were skipped
477 groupUUID, err := RemoteGroupExists(s.cfg, "TestGroup5")
479 c.Assert(groupUUID, Equals, "")
480 // Confirm that membership exists
481 groupUUID, err = RemoteGroupExists(s.cfg, "TestGroup4")
483 c.Assert(groupUUID, Not(Equals), "")
484 c.Assert(GroupMembershipExists(s.cfg.Client, activeUserUUID, groupUUID, "can_write"), Equals, true)
487 // Instead of emails, use username as identifier
488 func (s *TestSuite) TestUseUsernames(c *C) {
489 activeUserName := s.users[arvadostest.ActiveUserUUID].Username
490 activeUserUUID := s.users[arvadostest.ActiveUserUUID].UUID
491 // Confirm that group doesn't exist
492 groupUUID, err := RemoteGroupExists(s.cfg, "TestGroup1")
494 c.Assert(groupUUID, Equals, "")
495 // Create file & run command
497 {"TestGroup1", activeUserName},
499 tmpfile, err := MakeTempCSVFile(data)
501 defer os.Remove(tmpfile.Name()) // clean up
502 s.cfg.Path = tmpfile.Name()
503 s.cfg.UserID = "username"
505 s.cfg.UserID = "email"
507 // Confirm that memberships exist
508 groupUUID, err = RemoteGroupExists(s.cfg, "TestGroup1")
510 c.Assert(groupUUID, Not(Equals), "")
511 c.Assert(GroupMembershipExists(s.cfg.Client, activeUserUUID, groupUUID, "can_write"), Equals, true)