access_test.go 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. package sharedspace
  2. import (
  3. "path/filepath"
  4. "testing"
  5. )
  6. // newAccessTestSpace builds a space with one admin ("adam") and one plain
  7. // member ("mia") besides the owner ("alice").
  8. func newAccessTestSpace(t *testing.T, access string) (*Manager, *Space) {
  9. t.Helper()
  10. m := NewManager(filepath.Join(t.TempDir(), "spaces"), 0)
  11. space, err := m.CreateSpaceWithOptions("alice", "ACL test", SpaceOptions{Access: access})
  12. if err != nil {
  13. t.Fatalf("CreateSpaceWithOptions() error = %v", err)
  14. }
  15. if err := space.AddMember("alice", "adam", RoleAdmin); err != nil {
  16. t.Fatalf("AddMember(adam) error = %v", err)
  17. }
  18. if err := space.AddMember("alice", "mia", RoleMember); err != nil {
  19. t.Fatalf("AddMember(mia) error = %v", err)
  20. }
  21. return m, space
  22. }
  23. func TestAccessMatrix(t *testing.T) {
  24. tests := []struct {
  25. access string
  26. username string
  27. wantRead bool
  28. wantPost bool
  29. wantManage bool
  30. }{
  31. {AccessOpen, "alice", true, true, true}, //owner
  32. {AccessOpen, "adam", true, true, true}, //space admin
  33. {AccessOpen, "mia", true, true, false}, //member
  34. {AccessOpen, "stranger", true, true, false},
  35. {AccessOpen, "", true, true, true}, //system authority
  36. {AccessPublic, "alice", true, true, true},
  37. {AccessPublic, "stranger", true, true, false},
  38. {AccessPublic, "", true, true, true},
  39. {AccessPrivate, "alice", true, true, true},
  40. {AccessPrivate, "adam", true, true, true},
  41. {AccessPrivate, "mia", true, true, false},
  42. {AccessPrivate, "stranger", false, false, false},
  43. {AccessPrivate, "", true, true, true},
  44. }
  45. for _, tt := range tests {
  46. t.Run(tt.access+"/"+tt.username, func(t *testing.T) {
  47. _, space := newAccessTestSpace(t, tt.access)
  48. if got := space.CanRead(tt.username); got != tt.wantRead {
  49. t.Errorf("CanRead(%q) = %v, want %v", tt.username, got, tt.wantRead)
  50. }
  51. if got := space.CanPost(tt.username); got != tt.wantPost {
  52. t.Errorf("CanPost(%q) = %v, want %v", tt.username, got, tt.wantPost)
  53. }
  54. if got := space.CanManage(tt.username); got != tt.wantManage {
  55. t.Errorf("CanManage(%q) = %v, want %v", tt.username, got, tt.wantManage)
  56. }
  57. })
  58. }
  59. }
  60. func TestCreateSpaceWithOptionsValidation(t *testing.T) {
  61. m := NewManager(filepath.Join(t.TempDir(), "spaces"), 0)
  62. if _, err := m.CreateSpaceWithOptions("alice", "x", SpaceOptions{Access: "secret"}); err != ErrInvalidAccess {
  63. t.Errorf("invalid access error = %v, want ErrInvalidAccess", err)
  64. }
  65. //Memory-only manager cannot create persistent spaces
  66. if _, err := m.CreateSpaceWithOptions("alice", "x", SpaceOptions{Persistent: true}); err != ErrPersistenceOff {
  67. t.Errorf("persistent-on-memory-manager error = %v, want ErrPersistenceOff", err)
  68. }
  69. //Default access is open, owner is seeded as member
  70. space, err := m.CreateSpaceWithOptions("alice", "x", SpaceOptions{})
  71. if err != nil {
  72. t.Fatalf("CreateSpaceWithOptions() error = %v", err)
  73. }
  74. if space.AccessMode() != AccessOpen {
  75. t.Errorf("default access = %q, want open", space.AccessMode())
  76. }
  77. if role, ok := space.Role("alice"); !ok || role != RoleOwner {
  78. t.Errorf("owner role = %q/%v, want owner/true", role, ok)
  79. }
  80. //Initial metadata is sanitized in
  81. space2, _ := m.CreateSpaceWithOptions("alice", "x", SpaceOptions{
  82. Metadata: map[string]string{"purpose": "chat", "": "dropped"},
  83. })
  84. if meta := space2.Metadata(); meta["purpose"] != "chat" || len(meta) != 1 {
  85. t.Errorf("initial metadata = %v, want {purpose:chat}", meta)
  86. }
  87. }
  88. func TestMembershipManagement(t *testing.T) {
  89. _, space := newAccessTestSpace(t, AccessPrivate)
  90. tests := []struct {
  91. name string
  92. action func() error
  93. wantErr error
  94. }{
  95. {"stranger cannot invite", func() error { return space.AddMember("stranger", "eve", RoleMember) }, ErrPermissionDenied},
  96. {"member cannot invite", func() error { return space.AddMember("mia", "eve", RoleMember) }, ErrPermissionDenied},
  97. {"invalid role rejected", func() error { return space.AddMember("alice", "eve", "superuser") }, ErrInvalidRole},
  98. {"owner role not grantable", func() error { return space.AddMember("alice", "eve", RoleOwner) }, ErrInvalidRole},
  99. {"admin can invite", func() error { return space.AddMember("adam", "eve", RoleMember) }, nil},
  100. {"duplicate invite rejected", func() error { return space.AddMember("alice", "eve", RoleMember) }, ErrMemberExists},
  101. {"owner cannot be removed", func() error { return space.RemoveMember("alice", "alice") }, ErrPermissionDenied},
  102. {"stranger cannot remove", func() error { return space.RemoveMember("stranger", "mia") }, ErrPermissionDenied},
  103. {"self-leave allowed", func() error { return space.RemoveMember("eve", "eve") }, nil},
  104. {"remove non-member", func() error { return space.RemoveMember("alice", "eve") }, ErrNotMember},
  105. {"owner role immutable", func() error { return space.SetMemberRole("alice", "alice", RoleAdmin) }, ErrPermissionDenied},
  106. {"member cannot change roles", func() error { return space.SetMemberRole("mia", "mia", RoleAdmin) }, ErrPermissionDenied},
  107. {"promote member to admin", func() error { return space.SetMemberRole("alice", "mia", RoleAdmin) }, nil},
  108. }
  109. for _, tt := range tests {
  110. t.Run(tt.name, func(t *testing.T) {
  111. if err := tt.action(); err != tt.wantErr {
  112. t.Errorf("error = %v, want %v", err, tt.wantErr)
  113. }
  114. })
  115. }
  116. if role, _ := space.Role("mia"); role != RoleAdmin {
  117. t.Errorf("mia role after promotion = %q, want admin", role)
  118. }
  119. if space.MemberCount() != 3 {
  120. t.Errorf("MemberCount() = %d, want 3 (alice, adam, mia)", space.MemberCount())
  121. }
  122. }
  123. func TestJoinPublic(t *testing.T) {
  124. tests := []struct {
  125. access string
  126. wantErr error
  127. }{
  128. {AccessOpen, nil},
  129. {AccessPublic, nil},
  130. {AccessPrivate, ErrPermissionDenied},
  131. }
  132. for _, tt := range tests {
  133. t.Run(tt.access, func(t *testing.T) {
  134. _, space := newAccessTestSpace(t, tt.access)
  135. if err := space.JoinPublic("newbie"); err != tt.wantErr {
  136. t.Fatalf("JoinPublic() error = %v, want %v", err, tt.wantErr)
  137. }
  138. if tt.wantErr == nil {
  139. if role, ok := space.Role("newbie"); !ok || role != RoleMember {
  140. t.Errorf("joined role = %q/%v, want member", role, ok)
  141. }
  142. if err := space.JoinPublic("newbie"); err != ErrMemberExists {
  143. t.Errorf("re-join error = %v, want ErrMemberExists", err)
  144. }
  145. }
  146. })
  147. }
  148. }
  149. func TestSetAccessAndMetadata(t *testing.T) {
  150. _, space := newAccessTestSpace(t, AccessOpen)
  151. if err := space.SetAccess("mia", AccessPrivate); err != ErrPermissionDenied {
  152. t.Errorf("member SetAccess error = %v, want ErrPermissionDenied", err)
  153. }
  154. if err := space.SetAccess("alice", "bogus"); err != ErrInvalidAccess {
  155. t.Errorf("bogus access error = %v, want ErrInvalidAccess", err)
  156. }
  157. if err := space.SetAccess("alice", AccessPrivate); err != nil {
  158. t.Fatalf("owner SetAccess error = %v", err)
  159. }
  160. if space.AccessMode() != AccessPrivate {
  161. t.Errorf("access = %q after SetAccess, want private", space.AccessMode())
  162. }
  163. if space.CanRead("stranger") {
  164. t.Errorf("stranger can still read after going private")
  165. }
  166. if err := space.SetMeta("mia", "k", "v"); err != ErrPermissionDenied {
  167. t.Errorf("member SetMeta error = %v, want ErrPermissionDenied", err)
  168. }
  169. if err := space.SetMeta("alice", "purpose", "standup notes"); err != nil {
  170. t.Fatalf("SetMeta error = %v", err)
  171. }
  172. if space.Metadata()["purpose"] != "standup notes" {
  173. t.Errorf("metadata not stored: %v", space.Metadata())
  174. }
  175. //Empty value deletes the key
  176. if err := space.SetMeta("alice", "purpose", ""); err != nil {
  177. t.Fatalf("SetMeta delete error = %v", err)
  178. }
  179. if _, exists := space.Metadata()["purpose"]; exists {
  180. t.Errorf("metadata key survived deletion")
  181. }
  182. //Entry cap
  183. for i := 0; i < maxMetadataKeys; i++ {
  184. if err := space.SetMeta("alice", "key"+string(rune('a'+i%26))+string(rune('a'+i/26)), "v"); err != nil {
  185. t.Fatalf("SetMeta cap-fill error = %v", err)
  186. }
  187. }
  188. if err := space.SetMeta("alice", "onemore", "v"); err != ErrMetadataLimit {
  189. t.Errorf("over-cap SetMeta error = %v, want ErrMetadataLimit", err)
  190. }
  191. }
  192. func TestSpaceListings(t *testing.T) {
  193. m := NewManager(filepath.Join(t.TempDir(), "spaces"), 0)
  194. m.CreateSpaceWithOptions("alice", "Open", SpaceOptions{})
  195. pub, _ := m.CreateSpaceWithOptions("alice", "Public", SpaceOptions{Access: AccessPublic})
  196. priv, _ := m.CreateSpaceWithOptions("bob", "Private", SpaceOptions{Access: AccessPrivate})
  197. priv.AddMember("bob", "alice", RoleMember)
  198. publicSpaces := m.ListPublicSpaces()
  199. if len(publicSpaces) != 1 || publicSpaces[0].ID != pub.ID {
  200. t.Errorf("ListPublicSpaces() returned %d spaces, want just the public one", len(publicSpaces))
  201. }
  202. if got := len(m.ListSpacesByMember("alice")); got != 3 {
  203. t.Errorf("alice is member of %d spaces, want 3", got)
  204. }
  205. if got := len(m.ListSpacesByMember("bob")); got != 1 {
  206. t.Errorf("bob is member of %d spaces, want 1", got)
  207. }
  208. if got := len(m.ListSpaces()); got != 3 {
  209. t.Errorf("ListSpaces() = %d, want 3", got)
  210. }
  211. }
  212. func TestAdminCanRemoveOthersItems(t *testing.T) {
  213. _, space := newAccessTestSpace(t, AccessOpen)
  214. item, err := space.AddText("mia", "my message", "test")
  215. if err != nil {
  216. t.Fatalf("AddText() error = %v", err)
  217. }
  218. //A space admin who is neither uploader nor owner may remove items
  219. if err := space.RemoveItem(item.ID, "adam"); err != nil {
  220. t.Errorf("admin RemoveItem error = %v, want nil", err)
  221. }
  222. //A plain member may not remove someone else's item
  223. item2, _ := space.AddText("adam", "admin note", "test")
  224. if err := space.RemoveItem(item2.ID, "mia"); err != ErrPermissionDenied {
  225. t.Errorf("member RemoveItem error = %v, want ErrPermissionDenied", err)
  226. }
  227. }
  228. func TestSpaceDiskUsage(t *testing.T) {
  229. m, space := newAccessTestSpace(t, AccessOpen)
  230. space.AddText("alice", "12345", "test")
  231. if usage := m.SpaceDiskUsage(space.ID); usage != 5 {
  232. t.Errorf("SpaceDiskUsage() = %d, want 5", usage)
  233. }
  234. if usage := m.SpaceDiskUsage("nonexistent"); usage != 0 {
  235. t.Errorf("SpaceDiskUsage(unknown) = %d, want 0", usage)
  236. }
  237. }