Skip to content

Commit 12c9e79

Browse files
committed
chore: add unit tests
1 parent 079dd09 commit 12c9e79

File tree

13 files changed

+1377
-0
lines changed

13 files changed

+1377
-0
lines changed
Lines changed: 285 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,285 @@
1+
package utils
2+
3+
import (
4+
"go-drive/common/types"
5+
"testing"
6+
)
7+
8+
func ptr(s string) *string { return &s }
9+
10+
func TestNewPermMap(t *testing.T) {
11+
root := ""
12+
perms := []types.PathPermission{
13+
{ID: 1, Path: &root, Subject: types.AnySubject, Permission: types.PermissionRead, Policy: types.PolicyAccept},
14+
{ID: 2, Path: ptr("a"), Subject: types.UserSubject("alice"), Permission: types.PermissionReadWrite, Policy: types.PolicyAccept},
15+
}
16+
pm := NewPermMap(perms)
17+
if pm == nil {
18+
t.Fatal("NewPermMap returned nil")
19+
}
20+
if len(pm) != 2 {
21+
t.Errorf("PermMap len: want 2, got %d", len(pm))
22+
}
23+
// ResolvePath behavior is covered in TestPermMap_ResolvePath below.
24+
p := pm.ResolvePath("")
25+
if !p.Readable() {
26+
t.Errorf("root: want readable, got %v", p)
27+
}
28+
p2 := pm.ResolvePath("a")
29+
if !p2.Writable() {
30+
t.Errorf("a: want writable, got %v", p2)
31+
}
32+
}
33+
34+
func TestPermMap_Filter(t *testing.T) {
35+
root := ""
36+
perms := []types.PathPermission{
37+
{ID: 1, Path: &root, Subject: types.AnySubject, Permission: types.PermissionRead, Policy: types.PolicyAccept},
38+
{ID: 2, Path: ptr("private"), Subject: types.UserSubject("alice"), Permission: types.PermissionReadWrite, Policy: types.PolicyAccept},
39+
}
40+
pm := NewPermMap(perms)
41+
42+
// Anonymous session: only ANY subject (root read); private inherits read but not write.
43+
sessionAnon := types.NewSession()
44+
filtered := pm.Filter(sessionAnon)
45+
if len(filtered) != 1 {
46+
t.Errorf("anonymous Filter: want 1 subject, got %d", len(filtered))
47+
}
48+
p := filtered.ResolvePath("private")
49+
if !p.Readable() {
50+
t.Errorf("anonymous on private: inherit read from root, got readable=%v", p.Readable())
51+
}
52+
if p.Writable() {
53+
t.Errorf("anonymous on private: want no write (no u:alice), got writable=%v", p.Writable())
54+
}
55+
56+
// Logged-in user alice: should have ANY + u:alice.
57+
sessionAlice := types.Session{
58+
User: types.User{Username: "alice", Groups: nil},
59+
Props: types.SM{},
60+
}
61+
filtered = pm.Filter(sessionAlice)
62+
if len(filtered) != 2 {
63+
t.Errorf("alice Filter: want 2 subjects, got %d", len(filtered))
64+
}
65+
p = filtered.ResolvePath("private")
66+
if !p.Readable() || !p.Writable() {
67+
t.Errorf("alice on private: want rw, got %v", p)
68+
}
69+
70+
// Admin user: should get privilegedPermMap (root read+write).
71+
sessionAdmin := types.Session{
72+
User: types.User{Username: "admin", Groups: []types.Group{{Name: types.AdminUserGroup}}},
73+
Props: types.SM{},
74+
}
75+
filtered = pm.Filter(sessionAdmin)
76+
// privilegedPermMap grants root read+write.
77+
rootPerm := filtered.ResolvePath("")
78+
if !rootPerm.Readable() || !rootPerm.Writable() {
79+
t.Errorf("admin root: want read+write, got %v", rootPerm)
80+
}
81+
anyPath := filtered.ResolvePath("any/path")
82+
if !anyPath.Readable() || !anyPath.Writable() {
83+
t.Errorf("admin any path: want read+write, got %v", anyPath)
84+
}
85+
}
86+
87+
func TestPermMap_ResolvePath(t *testing.T) {
88+
root := ""
89+
tests := []struct {
90+
name string
91+
permissions []types.PathPermission
92+
path string
93+
wantRead bool
94+
wantWrite bool
95+
}{
96+
{
97+
name: "root accept read",
98+
permissions: []types.PathPermission{
99+
{Path: &root, Subject: types.AnySubject, Permission: types.PermissionRead, Policy: types.PolicyAccept},
100+
},
101+
path: "",
102+
wantRead: true,
103+
wantWrite: false,
104+
},
105+
{
106+
name: "root accept read_write",
107+
permissions: []types.PathPermission{
108+
{Path: &root, Subject: types.AnySubject, Permission: types.PermissionReadWrite, Policy: types.PolicyAccept},
109+
},
110+
path: "",
111+
wantRead: true,
112+
wantWrite: true,
113+
},
114+
{
115+
name: "subpath inherits from root",
116+
permissions: []types.PathPermission{
117+
{Path: &root, Subject: types.AnySubject, Permission: types.PermissionReadWrite, Policy: types.PolicyAccept},
118+
},
119+
path: "a/b/c",
120+
wantRead: true,
121+
wantWrite: true,
122+
},
123+
{
124+
name: "subpath override reject write",
125+
permissions: []types.PathPermission{
126+
{Path: &root, Subject: types.AnySubject, Permission: types.PermissionReadWrite, Policy: types.PolicyAccept},
127+
{Path: ptr("a"), Subject: types.AnySubject, Permission: types.PermissionWrite, Policy: types.PolicyReject},
128+
},
129+
path: "a",
130+
wantRead: true,
131+
wantWrite: false,
132+
},
133+
{
134+
name: "deeper path still rejected write",
135+
permissions: []types.PathPermission{
136+
{Path: &root, Subject: types.AnySubject, Permission: types.PermissionReadWrite, Policy: types.PolicyAccept},
137+
{Path: ptr("a"), Subject: types.AnySubject, Permission: types.PermissionWrite, Policy: types.PolicyReject},
138+
},
139+
path: "a/b",
140+
wantRead: true,
141+
wantWrite: false,
142+
},
143+
{
144+
name: "user overrides anonymous",
145+
permissions: []types.PathPermission{
146+
{Path: &root, Subject: types.AnySubject, Permission: types.PermissionRead, Policy: types.PolicyAccept},
147+
{Path: ptr("private"), Subject: types.UserSubject("alice"), Permission: types.PermissionReadWrite, Policy: types.PolicyAccept},
148+
},
149+
path: "private",
150+
wantRead: true,
151+
wantWrite: true,
152+
},
153+
{
154+
name: "no permission for path",
155+
permissions: []types.PathPermission{
156+
{Path: ptr("a"), Subject: types.AnySubject, Permission: types.PermissionRead, Policy: types.PolicyAccept},
157+
},
158+
path: "b",
159+
wantRead: false,
160+
wantWrite: false,
161+
},
162+
{
163+
name: "empty PermMap",
164+
permissions: nil,
165+
path: "",
166+
wantRead: false,
167+
wantWrite: false,
168+
},
169+
}
170+
for _, tt := range tests {
171+
t.Run(tt.name, func(t *testing.T) {
172+
var pm PermMap
173+
if tt.permissions != nil {
174+
pm = NewPermMap(tt.permissions)
175+
} else {
176+
pm = make(PermMap)
177+
}
178+
got := pm.ResolvePath(tt.path)
179+
if read := got.Readable(); read != tt.wantRead {
180+
t.Errorf("ResolvePath(%q).Readable() = %v, want %v", tt.path, read, tt.wantRead)
181+
}
182+
if write := got.Writable(); write != tt.wantWrite {
183+
t.Errorf("ResolvePath(%q).Writable() = %v, want %v", tt.path, write, tt.wantWrite)
184+
}
185+
})
186+
}
187+
}
188+
189+
func TestPermMap_ResolveDescendant(t *testing.T) {
190+
root := ""
191+
t.Run("no descendants", func(t *testing.T) {
192+
perms := []types.PathPermission{
193+
{Path: &root, Subject: types.AnySubject, Permission: types.PermissionRead, Policy: types.PolicyAccept},
194+
}
195+
pm := NewPermMap(perms)
196+
_, defined := pm.ResolveDescendant("a/b")
197+
if defined {
198+
t.Error("ResolveDescendant(a/b): want defined=false when no descendants")
199+
}
200+
})
201+
202+
t.Run("descendants inherit and can restrict", func(t *testing.T) {
203+
perms := []types.PathPermission{
204+
{Path: &root, Subject: types.AnySubject, Permission: types.PermissionReadWrite, Policy: types.PolicyAccept},
205+
{Path: ptr("folder"), Subject: types.AnySubject, Permission: types.PermissionReadWrite, Policy: types.PolicyAccept},
206+
{Path: ptr("folder/secret"), Subject: types.AnySubject, Permission: types.PermissionWrite, Policy: types.PolicyReject},
207+
}
208+
pm := NewPermMap(perms)
209+
perm, defined := pm.ResolveDescendant("folder")
210+
if !defined {
211+
t.Fatal("ResolveDescendant(folder): want defined=true")
212+
}
213+
if !perm.Readable() {
214+
t.Errorf("folder descendant: want readable, got %v", perm)
215+
}
216+
if perm.Writable() {
217+
t.Errorf("folder descendant: want not writable (secret rejects write), got writable")
218+
}
219+
})
220+
221+
t.Run("root descendant is all paths", func(t *testing.T) {
222+
perms := []types.PathPermission{
223+
{Path: &root, Subject: types.AnySubject, Permission: types.PermissionRead, Policy: types.PolicyAccept},
224+
{Path: ptr("a"), Subject: types.AnySubject, Permission: types.PermissionReadWrite, Policy: types.PolicyAccept},
225+
}
226+
pm := NewPermMap(perms)
227+
perm, defined := pm.ResolveDescendant("")
228+
if !defined {
229+
t.Fatal("ResolveDescendant(): want defined=true")
230+
}
231+
// Merges all descendants: at least read, and a has write.
232+
if !perm.Readable() {
233+
t.Errorf("root descendant: want readable, got %v", perm)
234+
}
235+
if !perm.Writable() {
236+
t.Errorf("root descendant: want writable from a, got %v", perm)
237+
}
238+
})
239+
240+
t.Run("empty map", func(t *testing.T) {
241+
pm := make(PermMap)
242+
_, defined := pm.ResolveDescendant("x")
243+
if defined {
244+
t.Error("empty PermMap ResolveDescendant: want defined=false")
245+
}
246+
})
247+
}
248+
249+
// Same usage as permission_wrapper: Filter(session) first to get current user's PermMap, then ResolvePath/ResolveDescendant.
250+
func TestPermMap_FilterThenResolve(t *testing.T) {
251+
root := ""
252+
perms := []types.PathPermission{
253+
{Path: &root, Subject: types.AnySubject, Permission: types.PermissionRead, Policy: types.PolicyAccept},
254+
{Path: ptr("driveA"), Subject: types.AnySubject, Permission: types.PermissionRead, Policy: types.PolicyAccept},
255+
{Path: ptr("driveA/docs"), Subject: types.UserSubject("alice"), Permission: types.PermissionReadWrite, Policy: types.PolicyAccept},
256+
}
257+
pm := NewPermMap(perms)
258+
259+
// Anonymous: can only read root and driveA, cannot write driveA/docs (no alice subject).
260+
sessionAnon := types.NewSession()
261+
f := pm.Filter(sessionAnon)
262+
if f.ResolvePath("").Readable() != true || f.ResolvePath("driveA").Readable() != true {
263+
t.Errorf("anonymous: root and driveA should be readable")
264+
}
265+
if f.ResolvePath("driveA/docs").Writable() {
266+
t.Errorf("anonymous: driveA/docs should not be writable")
267+
}
268+
269+
// alice: driveA/docs is read-write.
270+
sessionAlice := types.Session{User: types.User{Username: "alice", Groups: nil}, Props: types.SM{}}
271+
f = pm.Filter(sessionAlice)
272+
if !f.ResolvePath("driveA/docs").Writable() {
273+
t.Errorf("alice: driveA/docs should be writable")
274+
}
275+
276+
// requireDescendant semantics: path itself and all descendants must satisfy the permission.
277+
dp, defined := f.ResolveDescendant("driveA")
278+
if !defined {
279+
t.Fatal("driveA should have descendants")
280+
}
281+
// docs has write, so merged descendant should have write.
282+
if !dp.Writable() {
283+
t.Errorf("alice ResolveDescendant(driveA): want writable, got %v", dp)
284+
}
285+
}

0 commit comments

Comments
 (0)