The BM chat context implements golem15.chat's LiveMembership, so every participant check also requires chat-group membership: a removed user loses # ogólny, their DMs, uploads, subscriptions and chat:message delivery at once instead of after the next participant sync.
140 lines
4.9 KiB
Go
140 lines
4.9 KiB
Go
package classes_test
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
|
|
"git.golem15.com/jakub/sm-bm-plugin/classes"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// members is an injected group check: user 7 is in studenci, user 9 is in
|
|
// moderatorzy only, user 13 makes the check fail.
|
|
func members(_ context.Context, _ *gorm.DB, userID uint, group string) (bool, error) {
|
|
switch {
|
|
case userID == 13:
|
|
return false, errors.New("database down")
|
|
case group == "studenci":
|
|
return userID == 7, nil
|
|
case group == "moderatorzy":
|
|
return userID == 9 || userID == 7, nil
|
|
}
|
|
return false, nil
|
|
}
|
|
|
|
func TestChatAccess(t *testing.T) {
|
|
db := func() *gorm.DB { return &gorm.DB{} }
|
|
access := classes.NewChatAccess(db, "studenci", members)
|
|
ctx := t.Context()
|
|
for _, c := range []struct {
|
|
name string
|
|
userID uint
|
|
contextType string
|
|
contextID uint
|
|
want bool
|
|
}{
|
|
{"member of bm/1", 7, "bm", 1, true},
|
|
{"other context type", 7, "channel", 1, false},
|
|
{"other context id", 7, "bm", 2, false},
|
|
{"non-member", 8, "bm", 1, false},
|
|
{"moderator only", 9, "bm", 1, false},
|
|
{"check fails", 13, "bm", 1, false},
|
|
{"no user", 0, "bm", 1, false},
|
|
} {
|
|
if got := access.CanAccessContext(ctx, c.userID, c.contextType, c.contextID); got != c.want {
|
|
t.Errorf("%s: CanAccessContext = %v, want %v", c.name, got, c.want)
|
|
}
|
|
if access.CanManageContext(ctx, c.userID, c.contextType, c.contextID) {
|
|
t.Errorf("%s: CanManageContext = true; nobody creates channels", c.name)
|
|
}
|
|
}
|
|
noDB := classes.NewChatAccess(func() *gorm.DB { return nil }, "studenci", members)
|
|
if noDB.CanAccessContext(ctx, 7, "bm", 1) {
|
|
t.Error("CanAccessContext without a database = true, want false")
|
|
}
|
|
nilFunc := classes.NewChatAccess(nil, "studenci", members)
|
|
if nilFunc.CanAccessContext(ctx, 7, "bm", 1) {
|
|
t.Error("CanAccessContext without a database func = true, want false")
|
|
}
|
|
noGroup := classes.NewChatAccess(db, "", members)
|
|
if noGroup.CanAccessContext(ctx, 7, "bm", 1) {
|
|
t.Error("CanAccessContext without a chat group = true, want false")
|
|
}
|
|
}
|
|
|
|
func TestChatContext(t *testing.T) {
|
|
ctx := t.Context()
|
|
c := classes.NewChatContext(&gorm.DB{}, "studenci", "moderatorzy").WithMembership(members)
|
|
if c.ID() != classes.ChatContextID || c.ID() != 1 || c.Type() != "bm" {
|
|
t.Errorf("context = %s/%d, want bm/1", c.Type(), c.ID())
|
|
}
|
|
if !c.ChatEnabled() || c.GroupName() != "ogólny" {
|
|
t.Errorf("ChatEnabled %v GroupName %q", c.ChatEnabled(), c.GroupName())
|
|
}
|
|
if c.BroadcastChannel() != "chat:context:1" {
|
|
t.Errorf("BroadcastChannel = %q, want chat:context:1", c.BroadcastChannel())
|
|
}
|
|
for _, id := range []uint{7, 9, 8} {
|
|
if ok, err := c.CanOverseeChats(ctx, id); ok || err != nil {
|
|
t.Errorf("CanOverseeChats(%d) = %v, %v; nobody oversees", id, ok, err)
|
|
}
|
|
}
|
|
if ok, err := c.HasMember(ctx, 7); !ok || err != nil {
|
|
t.Errorf("HasMember(7) = %v, %v, want true", ok, err)
|
|
}
|
|
if ok, err := c.HasMember(ctx, 9); ok || err != nil {
|
|
t.Errorf("HasMember(9) = %v, %v, want false (moderator group only)", ok, err)
|
|
}
|
|
if ok, _ := c.HasMember(ctx, 0); ok {
|
|
t.Error("HasMember(0) = true")
|
|
}
|
|
if _, err := c.HasMember(ctx, 13); err == nil {
|
|
t.Error("HasMember with a failing check returned no error")
|
|
}
|
|
if !c.RequireLiveMembership() {
|
|
t.Error("RequireLiveMembership = false, want true: removal from the chat group must revoke access at once")
|
|
}
|
|
if !c.CanActFor(ctx, 7, 7) || c.CanActFor(ctx, 7, 8) {
|
|
t.Error("CanActFor is not self-only")
|
|
}
|
|
|
|
// Moderators delete other people's messages in group conversations
|
|
// (# ogólny) only, and only while they are chat-group members too
|
|
// (D-10, D-24).
|
|
for _, mc := range []struct {
|
|
name string
|
|
userID uint
|
|
convType string
|
|
want bool
|
|
}{
|
|
{"moderator in group", 7, "group", true},
|
|
{"moderator in dm", 7, "dm", false},
|
|
{"moderator, unknown type", 7, "", false},
|
|
{"non-moderator in group", 8, "group", false},
|
|
{"moderator outside the chat group", 9, "group", false},
|
|
{"no user", 0, "group", false},
|
|
} {
|
|
got, err := c.CanModerateMessage(ctx, mc.userID, mc.convType)
|
|
if err != nil || got != mc.want {
|
|
t.Errorf("%s: CanModerateMessage = %v, %v, want %v", mc.name, got, err, mc.want)
|
|
}
|
|
}
|
|
if _, err := c.CanModerateMessage(ctx, 13, "group"); err == nil {
|
|
t.Error("CanModerateMessage with a failing check returned no error")
|
|
}
|
|
noMods := classes.NewChatContext(&gorm.DB{}, "studenci", "").WithMembership(members)
|
|
if ok, err := noMods.CanModerateMessage(ctx, 7, "group"); ok || err != nil {
|
|
t.Errorf("CanModerateMessage without a moderator group = %v, %v, want false", ok, err)
|
|
}
|
|
|
|
load := classes.ChatContextLoader(func() string { return "studenci" }, func() string { return "moderatorzy" })
|
|
if got, err := load(ctx, &gorm.DB{}, 2); got != nil || err != nil {
|
|
t.Errorf("loader(2) = %v, %v, want nil, nil", got, err)
|
|
}
|
|
got, err := load(ctx, &gorm.DB{}, 1)
|
|
if err != nil || got == nil || got.ID() != 1 || got.Type() != "bm" {
|
|
t.Errorf("loader(1) = %v, %v, want the bm/1 context", got, err)
|
|
}
|
|
}
|