refactor: unify user mode processing into shared service layer
Some checks failed
check / check (push) Failing after 2m28s
Some checks failed
check / check (push) Failing after 2m28s
Both the HTTP API and IRC wire protocol handlers now call service.ApplyUserMode/service.QueryUserMode for all user mode operations. The service layer iterates mode strings character by character (the correct IRC approach), ensuring identical behavior regardless of transport. Removed duplicate mode logic from internal/handlers/utility.go (buildUserModeString, applyUserModeChange, applyModeChar) and internal/ircserver/commands.go (buildUmodeString, inline iteration). Added service-level tests for QueryUserMode, ApplyUserMode (single-char, multi-char, invalid input, de-oper, +o rejection).
This commit is contained in:
@@ -363,3 +363,166 @@ func TestSendChannelMessage_Moderated(t *testing.T) {
|
||||
t.Errorf("operator should be able to send in moderated channel: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestQueryUserMode(t *testing.T) {
|
||||
env := newTestEnv(t)
|
||||
ctx := t.Context()
|
||||
|
||||
sid := createSession(ctx, t, env.db, "alice")
|
||||
|
||||
// Fresh session has no modes.
|
||||
modes := env.svc.QueryUserMode(ctx, sid)
|
||||
if modes != "+" {
|
||||
t.Errorf("expected +, got %s", modes)
|
||||
}
|
||||
|
||||
// Set wallops.
|
||||
_ = env.db.SetSessionWallops(ctx, sid, true)
|
||||
|
||||
modes = env.svc.QueryUserMode(ctx, sid)
|
||||
if modes != "+w" {
|
||||
t.Errorf("expected +w, got %s", modes)
|
||||
}
|
||||
|
||||
// Set oper.
|
||||
_ = env.db.SetSessionOper(ctx, sid, true)
|
||||
|
||||
modes = env.svc.QueryUserMode(ctx, sid)
|
||||
if modes != "+ow" {
|
||||
t.Errorf("expected +ow, got %s", modes)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyUserModeSingleChar(t *testing.T) {
|
||||
env := newTestEnv(t)
|
||||
ctx := t.Context()
|
||||
|
||||
sid := createSession(ctx, t, env.db, "alice")
|
||||
|
||||
// Apply +w.
|
||||
result, err := env.svc.ApplyUserMode(ctx, sid, "+w")
|
||||
if err != nil {
|
||||
t.Fatalf("apply +w: %v", err)
|
||||
}
|
||||
|
||||
if result != "+w" {
|
||||
t.Errorf("expected +w, got %s", result)
|
||||
}
|
||||
|
||||
// Apply -w.
|
||||
result, err = env.svc.ApplyUserMode(ctx, sid, "-w")
|
||||
if err != nil {
|
||||
t.Fatalf("apply -w: %v", err)
|
||||
}
|
||||
|
||||
if result != "+" {
|
||||
t.Errorf("expected +, got %s", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyUserModeMultiChar(t *testing.T) {
|
||||
env := newTestEnv(t)
|
||||
ctx := t.Context()
|
||||
|
||||
sid := createSession(ctx, t, env.db, "alice")
|
||||
|
||||
// Set oper first so we can test +wo (w applied, o
|
||||
// rejected because +o is not allowed via MODE).
|
||||
_ = env.db.SetSessionOper(ctx, sid, true)
|
||||
|
||||
// Apply +w alone should work.
|
||||
result, err := env.svc.ApplyUserMode(ctx, sid, "+w")
|
||||
if err != nil {
|
||||
t.Fatalf("apply +w: %v", err)
|
||||
}
|
||||
|
||||
if result != "+ow" {
|
||||
t.Errorf("expected +ow, got %s", result)
|
||||
}
|
||||
|
||||
// Reset wallops.
|
||||
_ = env.db.SetSessionWallops(ctx, sid, false)
|
||||
|
||||
// Multi-char -ow: should de-oper and remove wallops.
|
||||
_ = env.db.SetSessionWallops(ctx, sid, true)
|
||||
|
||||
result, err = env.svc.ApplyUserMode(ctx, sid, "-ow")
|
||||
if err != nil {
|
||||
t.Fatalf("apply -ow: %v", err)
|
||||
}
|
||||
|
||||
if result != "+" {
|
||||
t.Errorf("expected +, got %s", result)
|
||||
}
|
||||
|
||||
// +wo should fail because +o is not allowed.
|
||||
_, err = env.svc.ApplyUserMode(ctx, sid, "+wo")
|
||||
|
||||
var ircErr *service.IRCError
|
||||
if !errors.As(err, &ircErr) {
|
||||
t.Fatalf("expected IRCError, got %v", err)
|
||||
}
|
||||
|
||||
if ircErr.Code != irc.ErrUmodeUnknownFlag {
|
||||
t.Errorf(
|
||||
"expected ErrUmodeUnknownFlag, got %d",
|
||||
ircErr.Code,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyUserModeInvalidInput(t *testing.T) {
|
||||
env := newTestEnv(t)
|
||||
ctx := t.Context()
|
||||
|
||||
sid := createSession(ctx, t, env.db, "alice")
|
||||
|
||||
// Too short.
|
||||
_, err := env.svc.ApplyUserMode(ctx, sid, "+")
|
||||
|
||||
var ircErr *service.IRCError
|
||||
if !errors.As(err, &ircErr) {
|
||||
t.Fatalf("expected IRCError for short input, got %v", err)
|
||||
}
|
||||
|
||||
// Unknown flag.
|
||||
_, err = env.svc.ApplyUserMode(ctx, sid, "+x")
|
||||
if !errors.As(err, &ircErr) {
|
||||
t.Fatalf("expected IRCError for unknown flag, got %v", err)
|
||||
}
|
||||
|
||||
if ircErr.Code != irc.ErrUmodeUnknownFlag {
|
||||
t.Errorf(
|
||||
"expected ErrUmodeUnknownFlag, got %d",
|
||||
ircErr.Code,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyUserModeDeoper(t *testing.T) {
|
||||
env := newTestEnv(t)
|
||||
ctx := t.Context()
|
||||
|
||||
sid := createSession(ctx, t, env.db, "alice")
|
||||
|
||||
// Make oper via DB directly.
|
||||
_ = env.db.SetSessionOper(ctx, sid, true)
|
||||
|
||||
// -o should work.
|
||||
result, err := env.svc.ApplyUserMode(ctx, sid, "-o")
|
||||
if err != nil {
|
||||
t.Fatalf("apply -o: %v", err)
|
||||
}
|
||||
|
||||
if result != "+" {
|
||||
t.Errorf("expected +, got %s", result)
|
||||
}
|
||||
|
||||
// +o should fail.
|
||||
_, err = env.svc.ApplyUserMode(ctx, sid, "+o")
|
||||
|
||||
var ircErr *service.IRCError
|
||||
if !errors.As(err, &ircErr) {
|
||||
t.Fatalf("expected IRCError for +o, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user