test: cover the chat access policy and bootstrap guards

ChatAccess admits only chat-group members to context bm/1 and manages
nothing; the chat context oversees nothing and its loader knows only
id 1; /chat and /chat/members need a caller and a database. The
migration count rises to 15 for the chat group seed.
This commit is contained in:
2026-10-26 15:42:53 +01:00
parent 64601f6817
commit 07d67be1e7
4 changed files with 157 additions and 2 deletions

View File

@@ -0,0 +1,107 @@
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.CanActFor(ctx, 7, 7) || c.CanActFor(ctx, 7, 8) {
t.Error("CanActFor is not self-only")
}
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)
}
}