Skip to content

Commit 5a06ebf

Browse files
committed
Add extban tests
1 parent 797845a commit 5a06ebf

7 files changed

Lines changed: 769 additions & 9 deletions

File tree

tests/README.md

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,88 @@ def test_with_custom_nick(eggdrop_config, eggdrop_proc, mock_ircd, tcl_bridge):
253253
If you don't call `render()`, the `eggdrop_proc` fixture renders with
254254
defaults from `EggdropConfig.context()`.
255255

256+
`render()` only takes effect *before* the proc fixture evaluates. If your
257+
test parameter list pulls in `eggdrop_proc` directly, the proc has already
258+
spawned by the time the test body runs. To customise after the dataclass
259+
exists but before the bot starts, take `eggdrop_config` + `request:
260+
pytest.FixtureRequest`, render, then lazy-load the rest:
261+
262+
```python
263+
def test_loads_my_userfile(eggdrop_config, request: pytest.FixtureRequest):
264+
eggdrop_config.render(...)
265+
eggdrop_config.userfile_path.write_text(...) # tweak files post-render
266+
proc = request.getfixturevalue("eggdrop_proc")
267+
bridge = request.getfixturevalue("tcl_bridge")
268+
```
269+
270+
### Pre-populating the userfile
271+
272+
Two template variables (rendered by `templates/userfile.j2`) inject
273+
already-formatted ban-record lines into the userfile that's written
274+
before the bot starts:
275+
276+
- `userfile_ban_lines` — list of strings, written under `*ban - -`.
277+
- `userfile_chan_ban_lines` — dict of `chan-name → list of strings`,
278+
each list written under `::<chan-name> bans`. The channel must be
279+
configured before the userfile is read; the default eggdrop.conf
280+
`channels` list handles this for `#test` automatically (see
281+
`chanprog.c:452` for the order: conf load → HOOK_REHASH →
282+
`readuserfile`).
283+
284+
Build the strings with `support.userfile_helpers.format_userfile_ban`,
285+
which takes every field as a required keyword argument (`mask`, `perm`,
286+
`sticky`, `expire`, `added`, `lastactive`, `creator`, `desc`) and
287+
hex-escapes `:` / `\\` in the mask per `src/misc.c:str_escape`. The
288+
template itself is a flat iteration; all formatting lives in Python.
289+
290+
```python
291+
from support.userfile_helpers import format_userfile_ban
292+
293+
eggdrop_config.render(
294+
userfile_ban_lines=[
295+
format_userfile_ban(
296+
mask="a:storedacct", perm=True, sticky=False, expire=0,
297+
added=1700000000, lastactive=0, creator="owner",
298+
desc="from disk",
299+
),
300+
],
301+
userfile_chan_ban_lines={
302+
"#test": [
303+
format_userfile_ban(
304+
mask="~a:chanonlyacct", perm=True, sticky=False, expire=0,
305+
added=1700000000, lastactive=0, creator="owner",
306+
desc="per-chan",
307+
),
308+
],
309+
},
310+
)
311+
```
312+
313+
The chanfile is rendered from `templates/chanfile.j2`. Pass
314+
`chanfile_channels=[{"name": "#chan", "options": "..."}]` if you need
315+
to register channels at chanfile-load time (rather than via
316+
`channels` in the conf). Default is empty — most tests use the
317+
conf-level `channel add` instead.
318+
319+
### Selecting which modules to load
320+
321+
The `modules` template variable controls the `loadmodule` lines and gates
322+
the server-related conf block (`set net-type`, `server add`, `set
323+
msg-rate`, ...) on whether `server` is in the list. Default is the full
324+
chain needed for IRC behaviour: `["pbkdf2", "channels", "server", "ctcp",
325+
"irc", "console", "notes"]`.
326+
327+
Override to test channels-mod-only scenarios (e.g. behaviour when
328+
server.mod is absent — irc.mod and ctcp.mod will fail to load alongside
329+
since they `module_depend` on server):
330+
331+
```python
332+
eggdrop_config.render(modules=["pbkdf2", "channels", "console", "notes"])
333+
```
334+
335+
`extra_modules` still appends in addition to `modules`, so it's the right
336+
knob for opting into share/transfer/etc. without changing the base list.
337+
256338
## Layout
257339

258340
```

tests/conftest.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,20 @@ def context(self) -> dict[str, Any]:
8686
mod_path=str(REPO_ROOT) + "/",
8787
help_path=str(REPO_ROOT / "help") + "/",
8888
bridge_tcl_path=str(BRIDGE_TCL),
89+
modules=[
90+
"pbkdf2",
91+
"channels",
92+
"server",
93+
"ctcp",
94+
"irc",
95+
"console",
96+
"notes",
97+
],
8998
extra_modules=[],
9099
channels=[{"name": "#test", "chanmode": "+nt"}],
100+
chanfile_channels=[],
101+
userfile_ban_lines=[],
102+
userfile_chan_ban_lines={},
91103
extra_tcl="",
92104
server_cycle_wait=10,
93105
server_timeout=30,
@@ -111,8 +123,12 @@ def render(self, **overrides: Any) -> None:
111123
self.userfile_path.write_text(
112124
_jinja_env.get_template("userfile.j2").render(**ctx)
113125
)
114-
# channels module wants to fopen the chanfile read-write; touch it.
115-
self.chanfile_path.touch()
126+
# channels.mod wants to fopen the chanfile read-write; rendering
127+
# an empty `chanfile_channels` list produces an empty file, same
128+
# as touching it would.
129+
self.chanfile_path.write_text(
130+
_jinja_env.get_template("chanfile.j2").render(**ctx)
131+
)
116132
self.rendered = True
117133

118134

tests/support/userfile_helpers.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
"""Helpers for building userfile content programmatically.
2+
3+
Tests inject pre-populated ban records into the userfile via the
4+
`userfile_ban_lines` (global) and `userfile_chan_ban_lines` (per-channel)
5+
context variables of `templates/userfile.j2`. The template just emits
6+
the strings verbatim — all formatting and escaping happens here so the
7+
template stays a flat iteration.
8+
"""
9+
10+
from __future__ import annotations
11+
12+
13+
def format_userfile_ban(
14+
*,
15+
mask: str,
16+
perm: bool,
17+
sticky: bool,
18+
expire: int,
19+
added: int,
20+
lastactive: int,
21+
creator: str,
22+
desc: str,
23+
) -> str:
24+
"""Format one ban record for the userfile.
25+
26+
Mirrors the line written by `write_bans` in
27+
`src/mod/channels.mod/userchan.c`:
28+
29+
- <mask>:<perm-prefix><expire><sticky-suffix>:+<added>:<lastactive>:<creator>:<desc>
30+
31+
`:` and `\\` in the mask are hex-escaped per `src/misc.c:str_escape`
32+
(the parser uses `\\xy` as a hex byte; a literal `\\:` would yield NUL
33+
via strtol of ":?" base 16 and silently truncate the mask). All
34+
arguments are required — no defaults — so each test states exactly
35+
what it's pinning.
36+
"""
37+
escaped = mask.replace("\\", "\\5c").replace(":", "\\3a")
38+
perm_prefix = "+" if perm else ""
39+
sticky_suffix = "*" if sticky else ""
40+
return (
41+
f"- {escaped}:{perm_prefix}{expire}{sticky_suffix}:"
42+
f"+{added}:{lastactive}:{creator}:{desc}"
43+
)

tests/templates/chanfile.j2

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{#- Chanfile template.
2+
3+
Tests pass `chanfile_channels` as a list of {"name": ..., "options": ...}
4+
records. `options` is a Tcl-syntax options block (verbatim) — defaults to
5+
the empty block `{}`, which is enough to make findchan_by_dname() return
6+
the channel during userfile load. By default the list is empty and the
7+
chanfile is empty.
8+
9+
Channels added via the eggdrop.conf `channels` template variable are
10+
*also* registered (via `channel add` lines in the conf) and run before
11+
the chanfile, so most tests don't need to populate this file at all.
12+
-#}
13+
{%- for ch in chanfile_channels -%}
14+
channel add {{ ch.name }} { {{ ch.options | default('') }} }
15+
{% endfor -%}

tests/templates/eggdrop.conf.j2

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,9 @@
33
set mod-path "{{ mod_path }}"
44
set help-path "{{ help_path }}"
55

6-
loadmodule pbkdf2
7-
loadmodule channels
8-
loadmodule server
9-
loadmodule ctcp
10-
loadmodule irc
11-
loadmodule console
12-
loadmodule notes
6+
{% for m in modules %}
7+
loadmodule {{ m }}
8+
{% endfor %}
139
{% for m in extra_modules %}
1410
loadmodule "{{ m }}"
1511
{% endfor %}
@@ -27,6 +23,7 @@ set userfile "{{ userfile_path }}"
2723
set chanfile "{{ chanfile_path }}"
2824
set pidfile "{{ pidfile_path }}"
2925

26+
{% if 'server' in modules %}
3027
set net-type "Other"
3128
set default-port "{{ mock_ircd_port }}"
3229
set servers [list]
@@ -37,6 +34,7 @@ set server-timeout "{{ server_timeout }}"
3734
set msg-rate 0
3835
set flood-msg 0
3936
set flood-ctcp 0
37+
{% endif %}
4038

4139
set raw-log 1
4240
logfile {{ log_flags }} * "{{ logfile_path }}"

tests/templates/userfile.j2

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,29 @@
1+
{#- Userfile template.
2+
3+
Tests inject pre-formatted ban-record lines via:
4+
userfile_ban_lines — list of strings, written under `*ban - -`.
5+
userfile_chan_ban_lines — dict of chan-name -> list of strings,
6+
each written under `::<chan-name> bans`.
7+
8+
Build the strings in Python with `support.userfile_helpers.format_userfile_ban`.
9+
10+
The `*ban - -` section header is what `write_bans` (channels.mod/userchan.c)
11+
emits and what the parser needs — `newsplit` (src/misc.c:255) splits on
12+
literal ' ' only, so `*ban\n` keeps the trailing newline glued to the
13+
token and `rfc_casecmp("*ban\n", "*ban")` fails to match.
14+
-#}
115
#4v: eggdrop test harness -- {{ botnet_nick }} -- generated
216
{{ owner_handle }} - {{ owner_flags }}
317
- {{ owner_hostmask }}
18+
{% if userfile_ban_lines -%}
19+
*ban - -
20+
{% for line in userfile_ban_lines -%}
21+
{{ line }}
22+
{% endfor -%}
23+
{% endif -%}
24+
{% for chan_name, lines in userfile_chan_ban_lines.items() -%}
25+
::{{ chan_name }} bans
26+
{% for line in lines -%}
27+
{{ line }}
28+
{% endfor -%}
29+
{% endfor -%}

0 commit comments

Comments
 (0)