6d10595d4655a7ad96c339cb2ead154fd3c198d4
[arvados.git] / tools / sync-users / sync-users_test.go
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 package main
6
7 import (
8         "fmt"
9         "io/ioutil"
10         "os"
11         "strings"
12         "testing"
13
14         "git.arvados.org/arvados.git/sdk/go/arvados"
15         . "gopkg.in/check.v1"
16 )
17
18 // Gocheck boilerplate
19 func Test(t *testing.T) {
20         TestingT(t)
21 }
22
23 type TestSuite struct {
24         cfg   *ConfigParams
25         ac    *arvados.Client
26         users map[string]arvados.User
27 }
28
29 func (s *TestSuite) SetUpTest(c *C) {
30         s.ac = arvados.NewClientFromEnv()
31         u, err := s.ac.CurrentUser()
32         c.Assert(err, IsNil)
33         c.Assert(u.IsAdmin, Equals, true)
34
35         s.users = make(map[string]arvados.User)
36         ul := arvados.UserList{}
37         s.ac.RequestAndDecode(&ul, "GET", "/arvados/v1/users", nil, arvados.ResourceListParams{})
38         c.Assert(ul.ItemsAvailable, Not(Equals), 0)
39         s.users = make(map[string]arvados.User)
40         for _, u := range ul.Items {
41                 s.users[u.UUID] = u
42         }
43
44         // Set up command config
45         os.Args = []string{"cmd", "somefile.csv"}
46         config, err := GetConfig()
47         c.Assert(err, IsNil)
48         s.cfg = &config
49 }
50
51 func (s *TestSuite) TearDownTest(c *C) {
52         var dst interface{}
53         // Reset database to fixture state after every test run.
54         err := s.cfg.Client.RequestAndDecode(&dst, "POST", "/database/reset", nil, nil)
55         c.Assert(err, IsNil)
56 }
57
58 var _ = Suite(&TestSuite{})
59
60 // MakeTempCSVFile creates a temp file with data as comma separated values
61 func MakeTempCSVFile(data [][]string) (f *os.File, err error) {
62         f, err = ioutil.TempFile("", "test_sync_users")
63         if err != nil {
64                 return
65         }
66         for _, line := range data {
67                 fmt.Fprintf(f, "%s\n", strings.Join(line, ","))
68         }
69         err = f.Close()
70         return
71 }
72
73 func ListUsers(ac *arvados.Client) ([]arvados.User, error) {
74         var ul arvados.UserList
75         err := ac.RequestAndDecode(&ul, "GET", "/arvados/v1/users", nil, arvados.ResourceListParams{})
76         if err != nil {
77                 return nil, err
78         }
79         return ul.Items, nil
80 }
81
82 func (s *TestSuite) TestParseFlagsWithoutPositionalArgument(c *C) {
83         os.Args = []string{"cmd", "-verbose"}
84         err := ParseFlags(&ConfigParams{})
85         c.Assert(err, NotNil)
86 }
87
88 func (s *TestSuite) TestParseFlagsWithPositionalArgument(c *C) {
89         cfg := ConfigParams{}
90         os.Args = []string{"cmd", "/tmp/somefile.csv"}
91         err := ParseFlags(&cfg)
92         c.Assert(err, IsNil)
93         c.Assert(cfg.Path, Equals, "/tmp/somefile.csv")
94         c.Assert(cfg.Verbose, Equals, false)
95         c.Assert(cfg.DeactivateUnlisted, Equals, false)
96 }
97
98 func (s *TestSuite) TestParseFlagsWithOptionalFlags(c *C) {
99         cfg := ConfigParams{}
100         os.Args = []string{"cmd", "-verbose", "-deactivate-unlisted", "/tmp/somefile.csv"}
101         err := ParseFlags(&cfg)
102         c.Assert(err, IsNil)
103         c.Assert(cfg.Path, Equals, "/tmp/somefile.csv")
104         c.Assert(cfg.Verbose, Equals, true)
105         c.Assert(cfg.DeactivateUnlisted, Equals, true)
106 }
107
108 func (s *TestSuite) TestGetConfig(c *C) {
109         os.Args = []string{"cmd", "/tmp/somefile.csv"}
110         cfg, err := GetConfig()
111         c.Assert(err, IsNil)
112         c.Assert(cfg.AnonUserUUID, Not(Equals), "")
113         c.Assert(cfg.SysUserUUID, Not(Equals), "")
114         c.Assert(cfg.CurrentUser, Not(Equals), "")
115         c.Assert(cfg.ClusterID, Not(Equals), "")
116         c.Assert(cfg.Client, NotNil)
117 }
118
119 func (s *TestSuite) TestFailOnEmptyFields(c *C) {
120         records := [][]string{
121                 {"", "first-name", "last-name", "1", "0"},
122                 {"user@example", "", "last-name", "1", "0"},
123                 {"user@example", "first-name", "", "1", "0"},
124                 {"user@example", "first-name", "last-name", "", "0"},
125                 {"user@example", "first-name", "last-name", "1", ""},
126         }
127         for _, record := range records {
128                 data := [][]string{record}
129                 tmpfile, err := MakeTempCSVFile(data)
130                 c.Assert(err, IsNil)
131                 defer os.Remove(tmpfile.Name())
132                 s.cfg.Path = tmpfile.Name()
133                 err = doMain(s.cfg)
134                 c.Assert(err, NotNil)
135                 c.Assert(err, ErrorMatches, ".*fields cannot be empty.*")
136         }
137 }
138
139 func (s *TestSuite) TestIgnoreSpaces(c *C) {
140         // Make sure users aren't already there from fixtures
141         for _, user := range s.users {
142                 e := user.Email
143                 found := e == "user1@example.com" || e == "user2@example.com" || e == "user3@example.com"
144                 c.Assert(found, Equals, false)
145         }
146         // Use CSV data with leading/trailing whitespaces, confirm that they get ignored
147         data := [][]string{
148                 {" user1@example.com", "  Example", "   User1", "1", "0"},
149                 {"user2@example.com ", "Example  ", "User2   ", "1", "0"},
150                 {" user3@example.com ", "  Example  ", "   User3   ", "1", "0"},
151         }
152         tmpfile, err := MakeTempCSVFile(data)
153         c.Assert(err, IsNil)
154         defer os.Remove(tmpfile.Name())
155         s.cfg.Path = tmpfile.Name()
156         err = doMain(s.cfg)
157         c.Assert(err, IsNil)
158         users, err := ListUsers(s.cfg.Client)
159         c.Assert(err, IsNil)
160         for _, userNr := range []int{1, 2, 3} {
161                 found := false
162                 for _, user := range users {
163                         if user.Email == fmt.Sprintf("user%d@example.com", userNr) &&
164                                 user.LastName == fmt.Sprintf("User%d", userNr) &&
165                                 user.FirstName == "Example" && user.IsActive == true {
166                                 found = true
167                                 break
168                         }
169                 }
170                 c.Assert(found, Equals, true)
171         }
172 }
173
174 // Error out when records have != 5 records
175 func (s *TestSuite) TestWrongNumberOfFields(c *C) {
176         for _, testCase := range [][][]string{
177                 {{"user1@example.com", "Example", "User1", "1"}},
178                 {{"user1@example.com", "Example", "User1", "1", "0", "extra data"}},
179         } {
180                 tmpfile, err := MakeTempCSVFile(testCase)
181                 c.Assert(err, IsNil)
182                 defer os.Remove(tmpfile.Name())
183                 s.cfg.Path = tmpfile.Name()
184                 err = doMain(s.cfg)
185                 c.Assert(err, NotNil)
186                 c.Assert(err, ErrorMatches, ".*expected 5 fields, found.*")
187         }
188 }
189
190 // Error out when records have incorrect data types
191 func (s *TestSuite) TestWrongDataFields(c *C) {
192         for _, testCase := range [][][]string{
193                 {{"user1@example.com", "Example", "User1", "yep", "0"}},
194                 {{"user1@example.com", "Example", "User1", "1", "nope"}},
195         } {
196                 tmpfile, err := MakeTempCSVFile(testCase)
197                 c.Assert(err, IsNil)
198                 defer os.Remove(tmpfile.Name())
199                 s.cfg.Path = tmpfile.Name()
200                 err = doMain(s.cfg)
201                 c.Assert(err, NotNil)
202                 c.Assert(err, ErrorMatches, ".*parsing error at line.*[active|admin] status not recognized.*")
203         }
204 }
205
206 // Activate and deactivate users
207 func (s *TestSuite) TestUserCreationAndUpdate(c *C) {
208         testCases := []struct {
209                 Email     string
210                 FirstName string
211                 LastName  string
212                 Active    bool
213                 Admin     bool
214         }{{
215                 Email:     "user1@example.com",
216                 FirstName: "Example",
217                 LastName:  "User1",
218                 Active:    true,
219                 Admin:     false,
220         }, {
221                 Email:     "admin1@example.com",
222                 FirstName: "Example",
223                 LastName:  "Admin1",
224                 Active:    true,
225                 Admin:     true,
226         }}
227         // Make sure users aren't already there from fixtures
228         for _, user := range s.users {
229                 e := user.Email
230                 found := e == testCases[0].Email || e == testCases[1].Email
231                 c.Assert(found, Equals, false)
232         }
233         // User creation
234         data := [][]string{
235                 {testCases[0].Email, testCases[0].FirstName, testCases[0].LastName, fmt.Sprintf("%t", testCases[0].Active), fmt.Sprintf("%t", testCases[0].Admin)},
236                 {testCases[1].Email, testCases[1].FirstName, testCases[1].LastName, fmt.Sprintf("%t", testCases[1].Active), fmt.Sprintf("%t", testCases[1].Admin)},
237         }
238         tmpfile, err := MakeTempCSVFile(data)
239         c.Assert(err, IsNil)
240         defer os.Remove(tmpfile.Name())
241         s.cfg.Path = tmpfile.Name()
242         err = doMain(s.cfg)
243         c.Assert(err, IsNil)
244
245         users, err := ListUsers(s.cfg.Client)
246         c.Assert(err, IsNil)
247         for _, tc := range testCases {
248                 var foundUser arvados.User
249                 for _, user := range users {
250                         if user.Email == tc.Email {
251                                 foundUser = user
252                                 break
253                         }
254                 }
255                 c.Assert(foundUser, NotNil)
256                 c.Logf("Checking recently created user %q", foundUser.Email)
257                 c.Assert(foundUser.FirstName, Equals, tc.FirstName)
258                 c.Assert(foundUser.LastName, Equals, tc.LastName)
259                 c.Assert(foundUser.IsActive, Equals, true)
260                 c.Assert(foundUser.IsAdmin, Equals, tc.Admin)
261         }
262         // User deactivation
263         testCases[0].Active = false
264         testCases[1].Active = false
265         data = [][]string{
266                 {testCases[0].Email, testCases[0].FirstName, testCases[0].LastName, fmt.Sprintf("%t", testCases[0].Active), fmt.Sprintf("%t", testCases[0].Admin)},
267                 {testCases[1].Email, testCases[1].FirstName, testCases[1].LastName, fmt.Sprintf("%t", testCases[1].Active), fmt.Sprintf("%t", testCases[1].Admin)},
268         }
269         tmpfile, err = MakeTempCSVFile(data)
270         c.Assert(err, IsNil)
271         defer os.Remove(tmpfile.Name())
272         s.cfg.Path = tmpfile.Name()
273         err = doMain(s.cfg)
274         c.Assert(err, IsNil)
275
276         users, err = ListUsers(s.cfg.Client)
277         c.Assert(err, IsNil)
278         for _, tc := range testCases {
279                 var foundUser arvados.User
280                 for _, user := range users {
281                         if user.Email == tc.Email {
282                                 foundUser = user
283                                 break
284                         }
285                 }
286                 c.Assert(foundUser, NotNil)
287                 c.Logf("Checking recently deactivated user %q", foundUser.Email)
288                 c.Assert(foundUser.FirstName, Equals, tc.FirstName)
289                 c.Assert(foundUser.LastName, Equals, tc.LastName)
290                 c.Assert(foundUser.IsActive, Equals, false)
291                 c.Assert(foundUser.IsAdmin, Equals, false) // inactive users cannot be admins
292         }
293 }