- server.go: drop unused (*Server).serve int return (unparam) and
remove the dead exitCode field so cleanShutdown no longer writes
to a field nothing reads.
- service.go: rename range var ch -> modeChar in parseUserModeString
and the isKnownUserModeChar parameter (varnamelen).
- service_test.go: rename tc -> testCase (varnamelen); lift the
inline struct and caseState to package-level named types
(applyUserModeCase, applyUserModeCaseState) with every field
set explicitly (exhaustruct); split the 167-line case table into
four categorised helpers (funlen); extract the per-case runner
and outcome/state verifiers into helpers so TestApplyUserMode
drops below gocognit 30 and flattens the wantErr nestif block.
No changes to .golangci.yml, Makefile, Dockerfile, or CI config.
No //nolint was used to silence any of these findings.
docker build --no-cache . passes clean: 0 lint issues, all tests
pass with -race, binary compiles.
Mode parser (internal/service/service.go):
- Reject strings without leading + or - (e.g. "xw", "w", "") with
ERR_UMODEUNKNOWNFLAG instead of silently treating them as "-".
- Support multi-sign transitions: +w-o, -w+o, +o-w+w, -x+y, +y-x. The
active sign flips each time + or - is seen; subsequent letters apply
with the active sign.
- Atomic from caller's perspective: parse the whole string to a list of
ops first, reject the whole request on any unknown mode char, and only
then apply ops to the DB. Partial application of +w before rejecting
+o is gone.
- HTTP and IRC still share the same ApplyUserMode entry point.
Router race (internal/server/server.go):
- The fx OnStart hook previously spawned serve() in a goroutine that
called SetupRoutes asynchronously, while ServeHTTP delegated to
srv.router. Test harnesses (httptest wrapping srv as Handler) raced
against SetupRoutes writing srv.router vs ServeHTTP reading it,
producing the race detector failures in CI on main.
- SetupRoutes is now called synchronously inside OnStart before the
serve goroutine starts, so srv.router is fully initialized before any
request can reach ServeHTTP.
Tests (internal/service/service_test.go):
- Replaced the per-mode tests with a single table-driven TestApplyUserMode
that asserts both the returned mode string and the persisted DB state
(oper/wallops) for each case, including the malformed and multi-sign
cases above. The +wz case seeds wallops=true to prove the whole string
is rejected and +w is not partially applied.
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).
Rebase onto main to resolve conflicts from module path rename
(sneak.berlin/go/neoirc) and integration test addition.
- Update import paths in utility.go to new module path
- Add IRC wire protocol handlers for VERSION, ADMIN, INFO,
TIME, KILL, and WALLOPS to ircserver/commands.go
- Register all 6 new commands in the IRC command dispatch map
- Implement proper user MODE +w/-w support for WALLOPS
- Add WALLOPS relay delivery in relay.go
- Add integration tests for all 7 Tier 3 commands:
USERHOST, VERSION, ADMIN, INFO, TIME, KILL, WALLOPS
- Add newTestEnvWithOper helper for oper-dependent tests
Implement all 7 utility IRC commands from issue #87:
User commands:
- USERHOST: quick lookup of user@host for up to 5 nicks (RPL 302)
- VERSION: server version string using globals.Version (RPL 351)
- ADMIN: server admin contact info (RPL 256-259)
- INFO: server software info text (RPL 371/374)
- TIME: server local time in RFC format (RPL 391)
Oper commands:
- KILL: forcibly disconnect a user (requires is_oper), broadcasts
QUIT to all shared channels, cleans up sessions
- WALLOPS: broadcast message to all users with +w usermode
(requires is_oper)
Supporting changes:
- Add is_wallops column to sessions table in 001_initial.sql
- Add user mode +w tracking via MODE nick +w/-w
- User mode queries now return actual modes (+o, +w)
- MODE -o allows de-opering yourself; MODE +o rejected
- MODE for other users returns ERR_USERSDONTMATCH (502)
- Extract dispatch helpers to reduce dispatchCommand complexity
Tests cover all commands including error cases, oper checks,
user mode set/unset, KILL broadcast, WALLOPS delivery, and
edge cases (self-kill, nonexistent users, missing params).
closes#87