@@ -47,6 +47,12 @@ def drive_registration(
4747 Waits for the TCP connect, drains the bot's NICK + USER, then sends the
4848 welcome sequence (001-004 + optional 005 with `isupport_tokens` + 376).
4949 Returns once the welcome is on the wire.
50+
51+ To influence which IRCv3 caps the bot negotiates, construct the IRCd with
52+ `MockIrcd(advertised_caps=[...])` (typically via a local `mock_ircd`
53+ fixture override). The cap list has to be set at construction time
54+ because the bot sends `CAP LS 302` immediately on TCP connect — before
55+ this helper runs.
5056 """
5157 mock_ircd .wait_for_connect (timeout = 10.0 )
5258 for _ in range (2 ): # NICK and USER
@@ -59,6 +65,7 @@ def drive_join_with_names(
5965 members_with_prefix : str ,
6066 nick : str = "TestBot" ,
6167 server : str = "mock.test" ,
68+ member_accounts : dict [str , str ] | None = None ,
6269) -> str :
6370 """Drive a realistic post-registration channel JOIN to completion.
6471
@@ -68,10 +75,16 @@ def drive_join_with_names(
6875 3. send NAMES (353) with `members_with_prefix` + end (366)
6976 4. drain the bot's post-join queries:
7077 * `MODE #chan +b/+e/+I` → empty 368/349/347 end-of-list replies
71- * `WHO #chan` → one 352 per member (prefix symbols passed through to
72- the flags field so opchars-based op detection works) + 315
78+ * `WHO #chan ...` → if the bot sent a WHOX-style request (the
79+ `c%chnufat,222` form, used when WHOX ISUPPORT is on), reply with
80+ one 354 per member carrying the per-member account from
81+ `member_accounts` (default "*" = not logged in). Otherwise reply
82+ with one 352 per member. Either form ends with 315.
7383 5. leave `MODE #chan` (no list flag) unanswered so tests can send
7484 their own 324 reply
85+
86+ `member_accounts` maps member nick → account name; nicks not in the dict
87+ get "*". Only consulted on the WHOX path; ignored for plain WHO.
7588 Returns the channel name. Quiesces when no new lines arrive for ~300 ms
7689 or after a 5 s hard cap.
7790 """
@@ -86,16 +99,26 @@ def drive_join_with_names(
8699 members : list [tuple [str , str ]] = [
87100 split_member_prefix (t ) for t in members_with_prefix .split () if t
88101 ]
102+ accounts = member_accounts or {}
89103
90- def reply_who () -> None :
104+ def reply_who (whox : bool ) -> None :
91105 for member_nick , prefix_syms in members :
92106 ident = "u"
93107 host = "h.example.com"
94108 flags = "H" + prefix_syms # H = here (not away)
95- mock_ircd .send (
96- f":{ server } 352 { nick } { chan } { ident } { host } { server } "
97- f"{ member_nick } { flags } :0 { member_nick } "
98- )
109+ if whox :
110+ # 354 format from chan.c:got354:
111+ # ":<srv> 354 <botnick> 222 <chan> <user> <host> <nick> <flags> <account>"
112+ acct = accounts .get (member_nick , "*" )
113+ mock_ircd .send (
114+ f":{ server } 354 { nick } 222 { chan } { ident } { host } "
115+ f"{ member_nick } { flags } { acct } "
116+ )
117+ else :
118+ mock_ircd .send (
119+ f":{ server } 352 { nick } { chan } { ident } { host } { server } "
120+ f"{ member_nick } { flags } :0 { member_nick } "
121+ )
99122 mock_ircd .send (f":{ server } 315 { nick } { chan } :End of /WHO list." )
100123
101124 deadline = time .monotonic () + 5.0
@@ -116,7 +139,9 @@ def reply_who() -> None:
116139 f":{ server } 347 { nick } { chan } :End of Channel Invite List"
117140 )
118141 elif line .startswith (f"WHO { chan } " ):
119- reply_who ()
142+ # WHOX form is `WHO #chan c%chnufat,222`; eggdrop emits this when
143+ # use_354 (WHOX ISUPPORT) is on and parses replies from got354.
144+ reply_who (whox = ",222" in line )
120145 # MODE #chan (no list flag) — left for the test to answer with 324.
121146 # Anything else is silently drained.
122147 return chan
0 commit comments