1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
15 "git.arvados.org/arvados.git/sdk/go/arvados"
19 // Gocheck boilerplate
20 func Test(t *testing.T) {
24 type TestSuite struct {
27 users map[string]arvados.User
30 func (s *TestSuite) SetUpTest(c *C) {
31 s.ac = arvados.NewClientFromEnv()
32 u, err := s.ac.CurrentUser()
34 c.Assert(u.IsAdmin, Equals, true)
36 s.users = make(map[string]arvados.User)
37 ul := arvados.UserList{}
38 s.ac.RequestAndDecode(&ul, "GET", "/arvados/v1/users", nil, arvados.ResourceListParams{})
39 c.Assert(ul.ItemsAvailable, Not(Equals), 0)
40 s.users = make(map[string]arvados.User)
41 for _, u := range ul.Items {
45 // Set up command config
46 os.Args = []string{"cmd", "somefile.csv"}
47 config, err := GetConfig()
52 func (s *TestSuite) TearDownTest(c *C) {
54 // Reset database to fixture state after every test run.
55 err := s.cfg.Client.RequestAndDecode(&dst, "POST", "/database/reset", nil, nil)
59 var _ = Suite(&TestSuite{})
61 // MakeTempCSVFile creates a temp file with data as comma separated values
62 func MakeTempCSVFile(data [][]string) (f *os.File, err error) {
63 f, err = ioutil.TempFile("", "test_sync_users")
67 for _, line := range data {
68 fmt.Fprintf(f, "%s\n", strings.Join(line, ","))
74 // RecordsToStrings formats the input data suitable for MakeTempCSVFile
75 func RecordsToStrings(records []userRecord) [][]string {
77 for _, u := range records {
78 data = append(data, []string{
82 fmt.Sprintf("%t", u.Active),
83 fmt.Sprintf("%t", u.Admin)})
88 func ListUsers(ac *arvados.Client) ([]arvados.User, error) {
89 var ul arvados.UserList
90 err := ac.RequestAndDecode(&ul, "GET", "/arvados/v1/users", nil, arvados.ResourceListParams{})
97 func (s *TestSuite) TestParseFlagsWithoutPositionalArgument(c *C) {
98 os.Args = []string{"cmd", "-verbose"}
99 err := ParseFlags(&ConfigParams{})
100 c.Assert(err, NotNil)
103 func (s *TestSuite) TestParseFlagsWithPositionalArgument(c *C) {
104 cfg := ConfigParams{}
105 os.Args = []string{"cmd", "/tmp/somefile.csv"}
106 err := ParseFlags(&cfg)
108 c.Assert(cfg.Path, Equals, "/tmp/somefile.csv")
109 c.Assert(cfg.Verbose, Equals, false)
110 c.Assert(cfg.DeactivateUnlisted, Equals, false)
113 func (s *TestSuite) TestParseFlagsWithOptionalFlags(c *C) {
114 cfg := ConfigParams{}
115 os.Args = []string{"cmd", "-verbose", "-deactivate-unlisted", "/tmp/somefile.csv"}
116 err := ParseFlags(&cfg)
118 c.Assert(cfg.Path, Equals, "/tmp/somefile.csv")
119 c.Assert(cfg.Verbose, Equals, true)
120 c.Assert(cfg.DeactivateUnlisted, Equals, true)
123 func (s *TestSuite) TestGetConfig(c *C) {
124 os.Args = []string{"cmd", "/tmp/somefile.csv"}
125 cfg, err := GetConfig()
127 c.Assert(cfg.AnonUserUUID, Not(Equals), "")
128 c.Assert(cfg.SysUserUUID, Not(Equals), "")
129 c.Assert(cfg.CurrentUser, Not(Equals), "")
130 c.Assert(cfg.ClusterID, Not(Equals), "")
131 c.Assert(cfg.Client, NotNil)
134 func (s *TestSuite) TestFailOnEmptyFields(c *C) {
135 records := [][]string{
136 {"", "first-name", "last-name", "1", "0"},
137 {"user@example", "", "last-name", "1", "0"},
138 {"user@example", "first-name", "", "1", "0"},
139 {"user@example", "first-name", "last-name", "", "0"},
140 {"user@example", "first-name", "last-name", "1", ""},
142 for _, record := range records {
143 data := [][]string{record}
144 tmpfile, err := MakeTempCSVFile(data)
146 defer os.Remove(tmpfile.Name())
147 s.cfg.Path = tmpfile.Name()
149 c.Assert(err, NotNil)
150 c.Assert(err, ErrorMatches, ".*fields cannot be empty.*")
154 func (s *TestSuite) TestIgnoreSpaces(c *C) {
155 // Make sure users aren't already there from fixtures
156 for _, user := range s.users {
158 found := e == "user1@example.com" || e == "user2@example.com" || e == "user3@example.com"
159 c.Assert(found, Equals, false)
161 // Use CSV data with leading/trailing whitespaces, confirm that they get ignored
163 {" user1@example.com", " Example", " User1", "1", "0"},
164 {"user2@example.com ", "Example ", "User2 ", "1", "0"},
165 {" user3@example.com ", " Example ", " User3 ", "1", "0"},
167 tmpfile, err := MakeTempCSVFile(data)
169 defer os.Remove(tmpfile.Name())
170 s.cfg.Path = tmpfile.Name()
173 users, err := ListUsers(s.cfg.Client)
175 for _, userNr := range []int{1, 2, 3} {
177 for _, user := range users {
178 if user.Email == fmt.Sprintf("user%d@example.com", userNr) &&
179 user.LastName == fmt.Sprintf("User%d", userNr) &&
180 user.FirstName == "Example" && user.IsActive == true {
185 c.Assert(found, Equals, true)
189 // Error out when records have != 5 records
190 func (s *TestSuite) TestWrongNumberOfFields(c *C) {
191 for _, testCase := range [][][]string{
192 {{"user1@example.com", "Example", "User1", "1"}},
193 {{"user1@example.com", "Example", "User1", "1", "0", "extra data"}},
195 tmpfile, err := MakeTempCSVFile(testCase)
197 defer os.Remove(tmpfile.Name())
198 s.cfg.Path = tmpfile.Name()
200 c.Assert(err, NotNil)
201 c.Assert(err, ErrorMatches, ".*expected 5 fields, found.*")
205 // Error out when records have incorrect data types
206 func (s *TestSuite) TestWrongDataFields(c *C) {
207 for _, testCase := range [][][]string{
208 {{"user1@example.com", "Example", "User1", "yep", "0"}},
209 {{"user1@example.com", "Example", "User1", "1", "nope"}},
211 tmpfile, err := MakeTempCSVFile(testCase)
213 defer os.Remove(tmpfile.Name())
214 s.cfg.Path = tmpfile.Name()
216 c.Assert(err, NotNil)
217 c.Assert(err, ErrorMatches, ".*parsing error at line.*[active|admin] status not recognized.*")
221 // Activate and deactivate users
222 func (s *TestSuite) TestUserCreationAndUpdate(c *C) {
223 testCases := []userRecord{{
224 UserID: "user1@example.com",
225 FirstName: "Example",
230 UserID: "admin1@example.com",
231 FirstName: "Example",
236 // Make sure users aren't already there from fixtures
237 for _, user := range s.users {
240 for _, r := range testCases {
246 c.Assert(found, Equals, false)
249 tmpfile, err := MakeTempCSVFile(RecordsToStrings(testCases))
251 defer os.Remove(tmpfile.Name())
252 s.cfg.Path = tmpfile.Name()
256 users, err := ListUsers(s.cfg.Client)
258 for _, tc := range testCases {
259 var foundUser arvados.User
260 for _, user := range users {
261 if user.Email == tc.UserID {
266 c.Assert(foundUser, NotNil)
267 c.Logf("Checking recently created user %q", foundUser.Email)
268 c.Assert(foundUser.FirstName, Equals, tc.FirstName)
269 c.Assert(foundUser.LastName, Equals, tc.LastName)
270 c.Assert(foundUser.IsActive, Equals, true)
271 c.Assert(foundUser.IsAdmin, Equals, tc.Admin)
274 for idx := range testCases {
275 testCases[idx].Active = false
277 tmpfile, err = MakeTempCSVFile(RecordsToStrings(testCases))
279 defer os.Remove(tmpfile.Name())
280 s.cfg.Path = tmpfile.Name()
284 users, err = ListUsers(s.cfg.Client)
286 for _, tc := range testCases {
287 var foundUser arvados.User
288 for _, user := range users {
289 if user.Email == tc.UserID {
294 c.Assert(foundUser, NotNil)
295 c.Logf("Checking recently deactivated user %q", foundUser.Email)
296 c.Assert(foundUser.FirstName, Equals, tc.FirstName)
297 c.Assert(foundUser.LastName, Equals, tc.LastName)
298 c.Assert(foundUser.IsActive, Equals, false)
299 c.Assert(foundUser.IsAdmin, Equals, false) // inactive users cannot be admins
303 func (s *TestSuite) TestDeactivateUnlisted(c *C) {
304 localUserUuidRegex := regexp.MustCompile(fmt.Sprintf("^%s-tpzed-[0-9a-z]{15}$", s.cfg.ClusterID))
305 users, err := ListUsers(s.cfg.Client)
307 previouslyActiveUsers := 0
308 for _, u := range users {
309 if u.UUID == fmt.Sprintf("%s-tpzed-anonymouspublic", s.cfg.ClusterID) && !u.IsActive {
310 // Make sure the anonymous user is active for this test
312 err := UpdateUser(s.cfg.Client, u.UUID, &au, map[string]string{"is_active": "true"})
314 c.Assert(au.IsActive, Equals, true)
316 if localUserUuidRegex.MatchString(u.UUID) && u.IsActive {
317 previouslyActiveUsers++
320 // At least 3 active users: System root, Anonymous and the current user.
321 // Other active users should exist from fixture.
322 c.Logf("Initial active users count: %d", previouslyActiveUsers)
323 c.Assert(previouslyActiveUsers > 3, Equals, true)
325 s.cfg.DeactivateUnlisted = true
328 {"user1@example.com", "Example", "User1", "0", "0"},
330 tmpfile, err := MakeTempCSVFile(data)
332 defer os.Remove(tmpfile.Name())
333 s.cfg.Path = tmpfile.Name()
337 users, err = ListUsers(s.cfg.Client)
339 currentlyActiveUsers := 0
340 acceptableActiveUUIDs := map[string]bool{
341 fmt.Sprintf("%s-tpzed-000000000000000", s.cfg.ClusterID): true,
342 fmt.Sprintf("%s-tpzed-anonymouspublic", s.cfg.ClusterID): true,
343 s.cfg.CurrentUser.UUID: true,
345 remainingActiveUUIDs := map[string]bool{}
346 seenUserEmails := map[string]bool{}
347 for _, u := range users {
348 if _, ok := seenUserEmails[u.Email]; ok {
349 c.Errorf("Duplicated email address %q in user list (probably from fixtures). This test requires unique email addresses.", u.Email)
351 seenUserEmails[u.Email] = true
352 if localUserUuidRegex.MatchString(u.UUID) && u.IsActive {
353 c.Logf("Found remaining active user %q (%s)", u.Email, u.UUID)
354 _, ok := acceptableActiveUUIDs[u.UUID]
355 c.Assert(ok, Equals, true)
356 remainingActiveUUIDs[u.UUID] = true
357 currentlyActiveUsers++
360 // 3 active users remaining: System root, Anonymous and the current user.
361 c.Logf("Active local users remaining: %v", remainingActiveUUIDs)
362 c.Assert(currentlyActiveUsers, Equals, 3)
365 func (s *TestSuite) TestFailOnDuplicatedEmails(c *C) {
366 for i := range []int{1, 2} {
368 err := CreateUser(s.cfg.Client, &arvados.User{}, map[string]string{
369 "email": "somedupedemail@example.com",
370 "first_name": fmt.Sprintf("Duped %d", i),
371 "username": fmt.Sprintf("dupedemail%d", i),
374 "is_admin": fmt.Sprintf("%t", isAdmin),
380 {"user1@example.com", "Example", "User1", "0", "0"},
382 tmpfile, err := MakeTempCSVFile(data)
384 defer os.Remove(tmpfile.Name())
385 s.cfg.Path = tmpfile.Name()
387 c.Assert(err, NotNil)
388 c.Assert(err, ErrorMatches, "skipped.*duplicated email address.*")