Skip to content

Commit 6a91dfc

Browse files
committed
Write tests for last plugin
1 parent 3e827dd commit 6a91dfc

File tree

2 files changed

+146
-1
lines changed

2 files changed

+146
-1
lines changed

src/csbot/plugins/last.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ def last(self, nick, channel=None, msgtype=None):
2424
if msgtype is not None:
2525
search['type'] = msgtype
2626

27-
return self.db.find_one(search, sort=[('when', pymongo.DESCENDING)])
27+
# Additional sorting by _id to make sort order stable for messages that arrive in the same millisecond
28+
# (which sometimes happens during tests).
29+
return self.db.find_one(search, sort=[('when', pymongo.DESCENDING), ('_id', pymongo.DESCENDING)])
2830

2931
def last_message(self, nick, channel=None):
3032
"""Get the last message sent by a nick, optionally filtering

tests/test_plugin_last.py

+143
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
import asyncio
2+
3+
import pytest
4+
5+
from csbot.plugins.last import Last
6+
7+
8+
pytestmark = [
9+
pytest.mark.bot(config="""\
10+
["@bot"]
11+
plugins = ["mongodb", "last"]
12+
13+
[mongodb]
14+
mode = "mock"
15+
"""),
16+
pytest.mark.usefixtures("run_client"),
17+
]
18+
19+
20+
def diff_dict(actual: dict, expected: dict) -> dict:
21+
"""Find items in *expected* that are different at the same keys in *actual*, returning a dict
22+
mapping the offending key to a dict with "expected" and "actual" items."""
23+
diff = dict()
24+
for k, v in expected.items():
25+
actual_value = actual.get(k)
26+
expected_value = expected.get(k)
27+
if actual_value != expected_value:
28+
diff[k] = dict(actual=actual_value, expected=expected_value)
29+
return diff
30+
31+
32+
async def test_message_types(bot_helper):
33+
plugin: Last = bot_helper["last"]
34+
35+
# Starting state: should have no "last message" for a user
36+
assert plugin.last("Nick") is None
37+
assert plugin.last_message("Nick") is None
38+
assert plugin.last_action("Nick") is None
39+
assert plugin.last_command("Nick") is None
40+
41+
# Receive a PRIVMSG from the user
42+
await bot_helper.client.line_received(":Nick!~user@hostname PRIVMSG #channel :Example message")
43+
# Check that message was recorded correctly
44+
assert diff_dict(plugin.last("Nick"), {"nick": "Nick", "message": "Example message"}) == {}
45+
# Check that message was only recorded in the correct category
46+
assert plugin.last_message("Nick") == plugin.last("Nick")
47+
assert not plugin.last_action("Nick") == plugin.last("Nick")
48+
assert not plugin.last_command("Nick") == plugin.last("Nick")
49+
50+
# Receive a CTCP ACTION from the user (inside a PRIVMSG)
51+
await bot_helper.client.line_received(":Nick!~user@hostname PRIVMSG #channel :\x01ACTION emotes\x01")
52+
# Check that message was recorded correctly
53+
assert diff_dict(plugin.last("Nick"), {"nick": "Nick", "message": "emotes"}) == {}
54+
# Check that message was only recorded in the correct category
55+
assert not plugin.last_message("Nick") == plugin.last("Nick")
56+
assert plugin.last_action("Nick") == plugin.last("Nick")
57+
assert not plugin.last_command("Nick") == plugin.last("Nick")
58+
59+
# Receive a bot command from the user (inside a PRIVMSG)
60+
await bot_helper.client.line_received(":Nick!~user@hostname PRIVMSG #channel :!help")
61+
# Check that message was recorded correctly
62+
assert diff_dict(plugin.last("Nick"), {"nick": "Nick", "message": "!help"}) == {}
63+
# Check that message was only recorded in the correct category
64+
assert not plugin.last_message("Nick") == plugin.last("Nick")
65+
assert not plugin.last_action("Nick") == plugin.last("Nick")
66+
assert plugin.last_command("Nick") == plugin.last("Nick")
67+
68+
# Final confirmation that the "message", "action" and "command" message types were all recorded separately
69+
assert diff_dict(plugin.last_message("Nick"), {"nick": "Nick", "message": "Example message"}) == {}
70+
assert diff_dict(plugin.last_action("Nick"), {"nick": "Nick", "message": "emotes"}) == {}
71+
assert diff_dict(plugin.last_command("Nick"), {"nick": "Nick", "message": "!help"}) == {}
72+
73+
# Also there shouldn't be any records for a different nick
74+
assert plugin.last("OtherNick") is None
75+
76+
77+
async def test_channel_filter(bot_helper):
78+
plugin: Last = bot_helper["last"]
79+
80+
# Starting state: should have no "last message" for a user
81+
assert plugin.last("Nick") is None
82+
assert plugin.last("Nick", channel="#a") is None
83+
assert plugin.last("Nick", channel="#b") is None
84+
85+
# Receive a PRIVMSG from the user in #a
86+
await bot_helper.client.line_received(":Nick!~user@hostname PRIVMSG #a :Message A")
87+
# Check that the message was recorded correctly
88+
assert diff_dict(plugin.last("Nick"), {"nick": "Nick", "channel": "#a", "message": "Message A"}) == {}
89+
# Check that channel filter applies correctly
90+
assert plugin.last("Nick", channel="#a") == plugin.last("Nick")
91+
assert not plugin.last("Nick", channel="#b") == plugin.last("Nick")
92+
93+
# Receive a PRIVMSG from the user in #b
94+
await bot_helper.client.line_received(":Nick!~user@hostname PRIVMSG #b :Message B")
95+
# Check that the message was recorded correctly
96+
assert diff_dict(plugin.last("Nick"), {"nick": "Nick", "channel": "#b", "message": "Message B"}) == {}
97+
# Check that channel filter applies correctly
98+
assert not plugin.last("Nick", channel="#a") == plugin.last("Nick")
99+
assert plugin.last("Nick", channel="#b") == plugin.last("Nick")
100+
101+
# Final confirmation that the latest message for each channel is stored
102+
assert diff_dict(plugin.last("Nick", channel="#a"), {"nick": "Nick", "channel": "#a", "message": "Message A"}) == {}
103+
assert diff_dict(plugin.last("Nick", channel="#b"), {"nick": "Nick", "channel": "#b", "message": "Message B"}) == {}
104+
105+
# Also there shouldn't be any records for a different channel
106+
assert plugin.last("Nick", channel="#c") is None
107+
108+
109+
async def test_seen_command(bot_helper):
110+
bot_helper.reset_mock()
111+
112+
# !seen for a nick not yet seen
113+
await asyncio.wait(bot_helper.receive(":A!~user@hostname PRIVMSG #a :!seen B"))
114+
bot_helper.assert_sent("NOTICE #a :Nothing recorded for B")
115+
116+
# !seen for a nick only seen in a different channel
117+
await asyncio.wait(bot_helper.receive(":B!~user@hostname PRIVMSG #b :First message"))
118+
await asyncio.wait(bot_helper.receive(":A!~user@hostname PRIVMSG #a :!seen B"))
119+
bot_helper.assert_sent("NOTICE #a :Nothing recorded for B")
120+
121+
# !seen for nick seen in the same channel
122+
await asyncio.wait(bot_helper.receive(":A!~user@hostname PRIVMSG #b :!seen B"))
123+
bot_helper.assert_sent(lambda line: "<B> First message" in line)
124+
125+
# Now seen in both channels, !seen should only return the message relating to the current channel
126+
await asyncio.wait(bot_helper.receive(":B!~user@hostname PRIVMSG #a :Second message"))
127+
await asyncio.wait(bot_helper.receive(":A!~user@hostname PRIVMSG #a :!seen B"))
128+
bot_helper.assert_sent(lambda line: "<B> Second message" in line)
129+
await asyncio.wait(bot_helper.receive(":A!~user@hostname PRIVMSG #b :!seen B"))
130+
bot_helper.assert_sent(lambda line: "<B> First message" in line)
131+
132+
# !seen on own nick should get the !seen command itself (because it makes more sense than "Nothing recorded")
133+
await asyncio.wait(bot_helper.receive(":B!~user@hostname PRIVMSG #a :!seen B"))
134+
bot_helper.assert_sent(lambda line: "<B> !seen B" in line)
135+
136+
# Check different formatting for actions
137+
await asyncio.wait(bot_helper.receive(":B!~user@hostname PRIVMSG #a :\x01ACTION does something\x01"))
138+
await asyncio.wait(bot_helper.receive(":A!~user@hostname PRIVMSG #a :!seen B"))
139+
bot_helper.assert_sent(lambda line: "* B does something" in line)
140+
141+
# Error when bad message type is specified
142+
await asyncio.wait(bot_helper.receive(":A!~user@hostname PRIVMSG #a :!seen B foobar"))
143+
bot_helper.assert_sent("NOTICE #a :Bad filter: foobar. Accepted are \"message\", \"command\", and \"action\".")

0 commit comments

Comments
 (0)