test(discord): trim obfuscation tests to two invariants + one backfill regression
Seven near-identical predicate tests collapse into one invariant test (flag OR sentinel name; neither on a normal channel; no flags attribute is not obfuscated) plus the directory-filter test, per the repo's <=2-invariant-tests-per-fix rule. The explicit-id backfill path gets its own regression test in the existing backfill harness (fails on the unfixed code, verified by sabotage).
This commit is contained in:
@@ -1,13 +1,7 @@
|
||||
"""Tests for Discord channel-obfuscation handling.
|
||||
"""Discord channel obfuscation (Aug 2026 privacy change, HTTP omission mandatory Nov 16 2026):
|
||||
channels a bot lacks VIEW_CHANNEL on arrive named "___hidden___" with flag 1 << 17 set.
|
||||
Enumeration sites must skip them. #90154"""
|
||||
|
||||
Discord's August 2026 privacy change ("Channel Obfuscation") dispatches
|
||||
channels a bot lacks VIEW_CHANNEL on with the name "___hidden___", flag
|
||||
1 << 17 (CHANNEL_OBFUSCATED) set, and sensitive fields nulled. Enumeration
|
||||
sites (channel directory, missed-message backfill) must skip these
|
||||
placeholders — the HTTP API omits them entirely starting Nov 16, 2026.
|
||||
"""
|
||||
|
||||
import unittest
|
||||
from types import SimpleNamespace
|
||||
|
||||
from gateway.platforms.helpers import (
|
||||
@@ -18,72 +12,35 @@ from gateway.platforms.helpers import (
|
||||
|
||||
|
||||
def _channel(name="general", flag_value=0, cid=123):
|
||||
return SimpleNamespace(
|
||||
id=cid,
|
||||
name=name,
|
||||
flags=SimpleNamespace(value=flag_value),
|
||||
return SimpleNamespace(id=cid, name=name, flags=SimpleNamespace(value=flag_value))
|
||||
|
||||
|
||||
def test_flag_or_sentinel_name_marks_obfuscated():
|
||||
assert is_discord_channel_obfuscated(_channel(name="secret", flag_value=DISCORD_CHANNEL_OBFUSCATED_FLAG | (1 << 4)))
|
||||
# Older discord.py builds may not surface the flag bit; the name is the fallback signal.
|
||||
assert is_discord_channel_obfuscated(_channel(name=DISCORD_OBFUSCATED_CHANNEL_NAME))
|
||||
assert not is_discord_channel_obfuscated(_channel())
|
||||
assert not is_discord_channel_obfuscated(SimpleNamespace(id=1, name="ok")) # no flags attribute
|
||||
|
||||
|
||||
def test_build_discord_filters_hidden_channels(monkeypatch):
|
||||
from gateway import channel_directory as cd
|
||||
|
||||
guild = SimpleNamespace(
|
||||
name="TestGuild",
|
||||
text_channels=[
|
||||
_channel(name="general", cid=1),
|
||||
_channel(name="secret", flag_value=DISCORD_CHANNEL_OBFUSCATED_FLAG, cid=2),
|
||||
_channel(name=DISCORD_OBFUSCATED_CHANNEL_NAME, cid=3),
|
||||
],
|
||||
forum_channels=[
|
||||
_channel(name="forum-open", cid=4),
|
||||
_channel(name="forum-secret", flag_value=DISCORD_CHANNEL_OBFUSCATED_FLAG, cid=5),
|
||||
],
|
||||
)
|
||||
adapter = SimpleNamespace(_client=SimpleNamespace(guilds=[guild]))
|
||||
monkeypatch.setattr(cd, "_build_from_sessions", lambda platform: [])
|
||||
|
||||
channels = cd._build_discord(adapter)
|
||||
|
||||
class TestIsDiscordChannelObfuscated(unittest.TestCase):
|
||||
def test_normal_channel_not_obfuscated(self):
|
||||
self.assertFalse(is_discord_channel_obfuscated(_channel()))
|
||||
|
||||
def test_flag_marks_obfuscated(self):
|
||||
ch = _channel(name="whatever", flag_value=DISCORD_CHANNEL_OBFUSCATED_FLAG)
|
||||
self.assertTrue(is_discord_channel_obfuscated(ch))
|
||||
|
||||
def test_flag_combined_with_other_flags(self):
|
||||
ch = _channel(flag_value=DISCORD_CHANNEL_OBFUSCATED_FLAG | (1 << 4))
|
||||
self.assertTrue(is_discord_channel_obfuscated(ch))
|
||||
|
||||
def test_sentinel_name_marks_obfuscated_without_flag(self):
|
||||
# Older discord.py builds may not surface the new flag bit; the
|
||||
# sentinel name is the fallback signal.
|
||||
ch = _channel(name=DISCORD_OBFUSCATED_CHANNEL_NAME, flag_value=0)
|
||||
self.assertTrue(is_discord_channel_obfuscated(ch))
|
||||
|
||||
def test_missing_flags_attribute(self):
|
||||
ch = SimpleNamespace(id=1, name="ok")
|
||||
self.assertFalse(is_discord_channel_obfuscated(ch))
|
||||
|
||||
def test_non_int_flag_value_falls_back_to_name(self):
|
||||
ch = SimpleNamespace(id=1, name="ok", flags=SimpleNamespace(value=None))
|
||||
self.assertFalse(is_discord_channel_obfuscated(ch))
|
||||
|
||||
|
||||
class TestChannelDirectorySkipsObfuscated(unittest.TestCase):
|
||||
def test_build_discord_filters_hidden_channels(self):
|
||||
from gateway import channel_directory as cd
|
||||
|
||||
visible = _channel(name="general", cid=1)
|
||||
hidden_flag = _channel(name="secret", flag_value=DISCORD_CHANNEL_OBFUSCATED_FLAG, cid=2)
|
||||
hidden_name = _channel(name=DISCORD_OBFUSCATED_CHANNEL_NAME, cid=3)
|
||||
visible_forum = _channel(name="forum-open", cid=4)
|
||||
hidden_forum = _channel(
|
||||
name="forum-secret", flag_value=DISCORD_CHANNEL_OBFUSCATED_FLAG, cid=5
|
||||
)
|
||||
|
||||
guild = SimpleNamespace(
|
||||
name="TestGuild",
|
||||
text_channels=[visible, hidden_flag, hidden_name],
|
||||
forum_channels=[visible_forum, hidden_forum],
|
||||
)
|
||||
adapter = SimpleNamespace(_client=SimpleNamespace(guilds=[guild]))
|
||||
|
||||
original = cd._build_from_sessions
|
||||
cd._build_from_sessions = lambda platform: []
|
||||
try:
|
||||
channels = cd._build_discord(adapter)
|
||||
finally:
|
||||
cd._build_from_sessions = original
|
||||
|
||||
ids = {c["id"] for c in channels}
|
||||
self.assertEqual(ids, {"1", "4"})
|
||||
types = {c["id"]: c["type"] for c in channels}
|
||||
self.assertEqual(types["1"], "channel")
|
||||
self.assertEqual(types["4"], "forum")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
assert {c["id"]: c["type"] for c in channels} == {"1": "channel", "4": "forum"}
|
||||
|
||||
@@ -458,3 +458,18 @@ async def test_iter_candidates_keeps_latest_messages_when_window_exceeds_limit(a
|
||||
assert got == [2, 3, 4]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_iter_candidates_skips_obfuscated_channel_on_explicit_ids(adapter, monkeypatch):
|
||||
"""A channel the bot lost VIEW_CHANNEL on (Discord obfuscation) is dropped even when
|
||||
listed by explicit id — history reads on it always fail (#90154)."""
|
||||
from gateway.platforms.helpers import DISCORD_CHANNEL_OBFUSCATED_FLAG
|
||||
|
||||
visible = FakeChannel(channel_id=1, history_messages=[make_message(message_id=11)])
|
||||
hidden = FakeChannel(channel_id=2, history_messages=[make_message(message_id=22)])
|
||||
hidden.name = "___hidden___"
|
||||
hidden.flags = SimpleNamespace(value=DISCORD_CHANNEL_OBFUSCATED_FLAG)
|
||||
adapter._client.get_channel = lambda channel_id: {1: visible, 2: hidden}[channel_id]
|
||||
|
||||
got = [msg.id async for msg in adapter._iter_missed_message_backfill_candidates({"1", "2"})]
|
||||
|
||||
assert got == [11]
|
||||
|
||||
Reference in New Issue
Block a user