1// Copyright 2016 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5//go:build (aix || darwin || dragonfly || freebsd || (!android && linux) || netbsd || openbsd || solaris) && !cgo
6
7package user
8
9import (
10	"reflect"
11	"strings"
12	"testing"
13)
14
15var groupTests = []struct {
16	in   string
17	name string
18	gid  string
19}{
20	{testGroupFile, "nobody", "-2"},
21	{testGroupFile, "kmem", "2"},
22	{testGroupFile, "notinthefile", ""},
23	{testGroupFile, "comment", ""},
24	{testGroupFile, "plussign", ""},
25	{testGroupFile, "+plussign", ""},
26	{testGroupFile, "-minussign", ""},
27	{testGroupFile, "minussign", ""},
28	{testGroupFile, "emptyid", ""},
29	{testGroupFile, "invalidgid", ""},
30	{testGroupFile, "indented", "7"},
31	{testGroupFile, "# comment", ""},
32	{testGroupFile, "largegroup", "1000"},
33	{testGroupFile, "manymembers", "777"},
34	{"", "emptyfile", ""},
35}
36
37func TestFindGroupName(t *testing.T) {
38	for _, tt := range groupTests {
39		got, err := findGroupName(tt.name, strings.NewReader(tt.in))
40		if tt.gid == "" {
41			if err == nil {
42				t.Errorf("findGroupName(%s): got nil error, expected err", tt.name)
43				continue
44			}
45			switch terr := err.(type) {
46			case UnknownGroupError:
47				if terr.Error() != "group: unknown group "+tt.name {
48					t.Errorf("findGroupName(%s): got %v, want %v", tt.name, terr, tt.name)
49				}
50			default:
51				t.Errorf("findGroupName(%s): got unexpected error %v", tt.name, terr)
52			}
53		} else {
54			if err != nil {
55				t.Fatalf("findGroupName(%s): got unexpected error %v", tt.name, err)
56			}
57			if got.Gid != tt.gid {
58				t.Errorf("findGroupName(%s): got gid %v, want %s", tt.name, got.Gid, tt.gid)
59			}
60			if got.Name != tt.name {
61				t.Errorf("findGroupName(%s): got name %s, want %s", tt.name, got.Name, tt.name)
62			}
63		}
64	}
65}
66
67var groupIdTests = []struct {
68	in   string
69	gid  string
70	name string
71}{
72	{testGroupFile, "-2", "nobody"},
73	{testGroupFile, "2", "kmem"},
74	{testGroupFile, "notinthefile", ""},
75	{testGroupFile, "comment", ""},
76	{testGroupFile, "7", "indented"},
77	{testGroupFile, "4", ""},
78	{testGroupFile, "20", ""}, // row starts with a plus
79	{testGroupFile, "21", ""}, // row starts with a minus
80	{"", "emptyfile", ""},
81}
82
83func TestFindGroupId(t *testing.T) {
84	for _, tt := range groupIdTests {
85		got, err := findGroupId(tt.gid, strings.NewReader(tt.in))
86		if tt.name == "" {
87			if err == nil {
88				t.Errorf("findGroupId(%s): got nil error, expected err", tt.gid)
89				continue
90			}
91			switch terr := err.(type) {
92			case UnknownGroupIdError:
93				if terr.Error() != "group: unknown groupid "+tt.gid {
94					t.Errorf("findGroupId(%s): got %v, want %v", tt.name, terr, tt.name)
95				}
96			default:
97				t.Errorf("findGroupId(%s): got unexpected error %v", tt.name, terr)
98			}
99		} else {
100			if err != nil {
101				t.Fatalf("findGroupId(%s): got unexpected error %v", tt.name, err)
102			}
103			if got.Gid != tt.gid {
104				t.Errorf("findGroupId(%s): got gid %v, want %s", tt.name, got.Gid, tt.gid)
105			}
106			if got.Name != tt.name {
107				t.Errorf("findGroupId(%s): got name %s, want %s", tt.name, got.Name, tt.name)
108			}
109		}
110	}
111}
112
113const testUserFile = `   # Example user file
114root:x:0:0:root:/root:/bin/bash
115daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
116bin:x:2:3:bin:/bin:/usr/sbin/nologin
117     indented:x:3:3:indented:/dev:/usr/sbin/nologin
118sync:x:4:65534:sync:/bin:/bin/sync
119negative:x:-5:60:games:/usr/games:/usr/sbin/nologin
120man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
121allfields:x:6:12:mansplit,man2,man3,man4:/home/allfields:/usr/sbin/nologin
122+plussign:x:8:10:man:/var/cache/man:/usr/sbin/nologin
123-minussign:x:9:10:man:/var/cache/man:/usr/sbin/nologin
124
125malformed:x:27:12 # more:colons:after:comment
126
127struid:x:notanumber:12 # more:colons:after:comment
128
129# commented:x:28:12:commented:/var/cache/man:/usr/sbin/nologin
130      # commentindented:x:29:12:commentindented:/var/cache/man:/usr/sbin/nologin
131
132struid2:x:30:badgid:struid2name:/home/struid:/usr/sbin/nologin
133`
134
135var userIdTests = []struct {
136	in   string
137	uid  string
138	name string
139}{
140	{testUserFile, "-5", "negative"},
141	{testUserFile, "2", "bin"},
142	{testUserFile, "100", ""}, // not in the file
143	{testUserFile, "8", ""},   // plus sign, glibc doesn't find it
144	{testUserFile, "9", ""},   // minus sign, glibc doesn't find it
145	{testUserFile, "27", ""},  // malformed
146	{testUserFile, "28", ""},  // commented out
147	{testUserFile, "29", ""},  // commented out, indented
148	{testUserFile, "3", "indented"},
149	{testUserFile, "30", ""}, // the Gid is not valid, shouldn't match
150	{"", "1", ""},
151}
152
153func TestInvalidUserId(t *testing.T) {
154	_, err := findUserId("notanumber", strings.NewReader(""))
155	if err == nil {
156		t.Fatalf("findUserId('notanumber'): got nil error")
157	}
158	if want := "user: invalid userid notanumber"; err.Error() != want {
159		t.Errorf("findUserId('notanumber'): got %v, want %s", err, want)
160	}
161}
162
163func TestLookupUserId(t *testing.T) {
164	for _, tt := range userIdTests {
165		got, err := findUserId(tt.uid, strings.NewReader(tt.in))
166		if tt.name == "" {
167			if err == nil {
168				t.Errorf("findUserId(%s): got nil error, expected err", tt.uid)
169				continue
170			}
171			switch terr := err.(type) {
172			case UnknownUserIdError:
173				if want := "user: unknown userid " + tt.uid; terr.Error() != want {
174					t.Errorf("findUserId(%s): got %v, want %v", tt.name, terr, want)
175				}
176			default:
177				t.Errorf("findUserId(%s): got unexpected error %v", tt.name, terr)
178			}
179		} else {
180			if err != nil {
181				t.Fatalf("findUserId(%s): got unexpected error %v", tt.name, err)
182			}
183			if got.Uid != tt.uid {
184				t.Errorf("findUserId(%s): got uid %v, want %s", tt.name, got.Uid, tt.uid)
185			}
186			if got.Username != tt.name {
187				t.Errorf("findUserId(%s): got name %s, want %s", tt.name, got.Username, tt.name)
188			}
189		}
190	}
191}
192
193func TestLookupUserPopulatesAllFields(t *testing.T) {
194	u, err := findUsername("allfields", strings.NewReader(testUserFile))
195	if err != nil {
196		t.Fatal(err)
197	}
198	want := &User{
199		Username: "allfields",
200		Uid:      "6",
201		Gid:      "12",
202		Name:     "mansplit",
203		HomeDir:  "/home/allfields",
204	}
205	if !reflect.DeepEqual(u, want) {
206		t.Errorf("findUsername: got %#v, want %#v", u, want)
207	}
208}
209
210var userTests = []struct {
211	in   string
212	name string
213	uid  string
214}{
215	{testUserFile, "negative", "-5"},
216	{testUserFile, "bin", "2"},
217	{testUserFile, "notinthefile", ""},
218	{testUserFile, "indented", "3"},
219	{testUserFile, "plussign", ""},
220	{testUserFile, "+plussign", ""},
221	{testUserFile, "minussign", ""},
222	{testUserFile, "-minussign", ""},
223	{testUserFile, "   indented", ""},
224	{testUserFile, "commented", ""},
225	{testUserFile, "commentindented", ""},
226	{testUserFile, "malformed", ""},
227	{testUserFile, "# commented", ""},
228	{"", "emptyfile", ""},
229}
230
231func TestLookupUser(t *testing.T) {
232	for _, tt := range userTests {
233		got, err := findUsername(tt.name, strings.NewReader(tt.in))
234		if tt.uid == "" {
235			if err == nil {
236				t.Errorf("lookupUser(%s): got nil error, expected err", tt.uid)
237				continue
238			}
239			switch terr := err.(type) {
240			case UnknownUserError:
241				if want := "user: unknown user " + tt.name; terr.Error() != want {
242					t.Errorf("lookupUser(%s): got %v, want %v", tt.name, terr, want)
243				}
244			default:
245				t.Errorf("lookupUser(%s): got unexpected error %v", tt.name, terr)
246			}
247		} else {
248			if err != nil {
249				t.Fatalf("lookupUser(%s): got unexpected error %v", tt.name, err)
250			}
251			if got.Uid != tt.uid {
252				t.Errorf("lookupUser(%s): got uid %v, want %s", tt.name, got.Uid, tt.uid)
253			}
254			if got.Username != tt.name {
255				t.Errorf("lookupUser(%s): got name %s, want %s", tt.name, got.Username, tt.name)
256			}
257		}
258	}
259}
260