Skip to content

Commit b32cd08

Browse files
slingamnprogval
andauthored
initial test for CHATHISTORY TARGETS (#309)
* initial test for CHATHISTORY TARGETS This was originally written by Claude 3.7 Sonnet. * add another test * update sable hash Includes Libera-Chat/sable#159 --------- Co-authored-by: Val Lorentz <[email protected]>
1 parent e95a288 commit b32cd08

File tree

1 file changed

+247
-2
lines changed

1 file changed

+247
-2
lines changed

irctest/server_tests/chathistory.py

Lines changed: 247 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@
1818
# Keep this in sync with validate_chathistory()
1919
SUBCOMMANDS = ["LATEST", "BEFORE", "AFTER", "BETWEEN", "AROUND"]
2020

21-
MYSQL_PASSWORD = ""
22-
2321

2422
def skip_ngircd(f):
2523
@functools.wraps(f)
@@ -813,6 +811,253 @@ def validate_msg(msg):
813811
relay = self.getMessage(2)
814812
validate_msg(relay)
815813

814+
@pytest.mark.private_chathistory
815+
@skip_ngircd
816+
def testChathistoryTargets(self):
817+
"""Tests the CHATHISTORY TARGETS command, which lists channels the user has visible
818+
history in and users with which the user has exchanged direct messages."""
819+
c1 = random_name("foo")
820+
c2 = random_name("bar")
821+
c3 = random_name("baz")
822+
823+
# Register users
824+
self.controller.registerUser(self, c1, "sesame1")
825+
self.controller.registerUser(self, c2, "sesame2")
826+
self.controller.registerUser(self, c3, "sesame3")
827+
828+
# Connect with all capabilities
829+
self.connectClient(
830+
c1,
831+
capabilities=[
832+
"message-tags",
833+
"server-time",
834+
"echo-message",
835+
"batch",
836+
"labeled-response",
837+
"sasl",
838+
CHATHISTORY_CAP,
839+
],
840+
password="sesame1",
841+
skip_if_cap_nak=True,
842+
)
843+
844+
self.connectClient(
845+
c2,
846+
capabilities=[
847+
"message-tags",
848+
"server-time",
849+
"echo-message",
850+
"batch",
851+
"labeled-response",
852+
"sasl",
853+
CHATHISTORY_CAP,
854+
],
855+
password="sesame2",
856+
)
857+
858+
self.connectClient(
859+
c3,
860+
capabilities=[
861+
"message-tags",
862+
"server-time",
863+
"echo-message",
864+
"batch",
865+
"labeled-response",
866+
"sasl",
867+
CHATHISTORY_CAP,
868+
],
869+
password="sesame3",
870+
)
871+
872+
# Clear any initial messages
873+
self.getMessages(1)
874+
self.getMessages(2)
875+
self.getMessages(3)
876+
877+
# Create a few channels for testing
878+
ch1 = "#chan" + secrets.token_hex(8)
879+
ch2 = "#chan" + secrets.token_hex(8)
880+
881+
# Join channels and exchange messages
882+
self.joinChannel(1, ch1)
883+
self.joinChannel(2, ch1)
884+
self.joinChannel(1, ch2)
885+
self.joinChannel(3, ch2)
886+
self.getMessages(1)
887+
self.getMessages(2)
888+
self.getMessages(3)
889+
890+
target_times = {} # Store target and their latest message times
891+
892+
# Exchange messages between users and in channels
893+
# First, exchange messages in ch1
894+
self.sendLine(1, f"PRIVMSG {ch1} :Hello channel 1")
895+
self.getMessages(1)
896+
ch1_msg = self.getMessages(2)[0]
897+
target_times[ch1] = ch1_msg.tags.get("time", "")
898+
time.sleep(0.002)
899+
900+
# Then, DM between c1 and c2
901+
self.sendLine(1, f"PRIVMSG {c2} :Hello user 2")
902+
self.getMessages(1)
903+
c2_msg = self.getMessages(2)[0]
904+
target_times[c2] = c2_msg.tags.get("time", "")
905+
time.sleep(0.002)
906+
907+
# Then, messages in ch2
908+
self.sendLine(1, f"PRIVMSG {ch2} :Hello channel 2")
909+
self.getMessages(1)
910+
ch2_msg = self.getMessages(3)[0]
911+
target_times[ch2] = ch2_msg.tags.get("time", "")
912+
time.sleep(0.002)
913+
914+
# Finally, DM between c1 and c3
915+
self.sendLine(1, f"PRIVMSG {c3} :Hello user 3")
916+
self.getMessages(1)
917+
c3_msg = self.getMessages(3)[0]
918+
target_times[c3] = c3_msg.tags.get("time", "")
919+
920+
# Now test TARGETS command
921+
# Get a timestamp before all messages
922+
before_all = "timestamp=2020-01-01T00:00:00.000Z"
923+
# Get a timestamp after all messages
924+
after_all = "timestamp=2262-01-01T00:00:00.000Z"
925+
926+
# Execute TARGETS command
927+
self.sendLine(1, f"CHATHISTORY TARGETS {before_all} {after_all} 100")
928+
929+
# Verify response
930+
batch_messages = self.getMessages(1)
931+
932+
targets_results = self.summarize_chathistory_targets(batch_messages)
933+
# Check that each target we messaged is in the results
934+
# Targets should be sorted by time of latest message (earliest first)
935+
expected_order = [ch1, c2, ch2, c3]
936+
expected_results = [(target, target_times[target]) for target in expected_order]
937+
self.assertEqual(targets_results, expected_results)
938+
939+
# Test with a limit parameter
940+
self.sendLine(1, f"CHATHISTORY TARGETS {before_all} {after_all} 2")
941+
batch_messages = self.getMessages(1)
942+
943+
# Extract targets again
944+
targets_results = self.summarize_chathistory_targets(batch_messages)
945+
946+
# Should only get 2 targets due to limit
947+
self.assertEqual([t[0] for t in targets_results], [ch1, c2])
948+
949+
# Test with timestamp range that excludes some targets
950+
# Get the timestamp from the first message in ch1
951+
ch1_time = target_times[ch1]
952+
# Send the command to get targets after this time
953+
self.sendLine(1, f"CHATHISTORY TARGETS timestamp={ch1_time} {after_all} 100")
954+
batch_messages = self.getMessages(1)
955+
956+
# Should only get targets that had messages after ch1_time
957+
# That would be c2, ch2, and c3, but not ch1
958+
targets_results = self.summarize_chathistory_targets(batch_messages)
959+
self.assertEqual([t[0] for t in targets_results], [c2, ch2, c3])
960+
961+
# test limits on both sides
962+
ch3_time = target_times[c3]
963+
self.sendLine(
964+
1, f"CHATHISTORY TARGETS timestamp={ch1_time} timestamp={ch3_time} 100"
965+
)
966+
batch_messages = self.getMessages(1)
967+
targets_results = self.summarize_chathistory_targets(batch_messages)
968+
self.assertEqual([t[0] for t in targets_results], [c2, ch2])
969+
970+
@pytest.mark.private_chathistory
971+
@skip_ngircd
972+
def testChathistoryTargetsExcludesUpdatedTargets(self):
973+
"""Tests that CHATHISTORY TARGETS does not match targets that have messages
974+
within the selection window, but where the latest message is outside the
975+
selection window."""
976+
c1 = random_name("foo")
977+
978+
# Register users
979+
self.controller.registerUser(self, c1, "sesame1")
980+
981+
# Connect with all capabilities
982+
self.connectClient(
983+
c1,
984+
capabilities=[
985+
"message-tags",
986+
"server-time",
987+
"echo-message",
988+
"batch",
989+
"labeled-response",
990+
"sasl",
991+
CHATHISTORY_CAP,
992+
],
993+
password="sesame1",
994+
skip_if_cap_nak=True,
995+
)
996+
997+
# Clear any initial messages
998+
self.getMessages(1)
999+
1000+
# Create a few channels for testing
1001+
ch1 = "#chan" + secrets.token_hex(8)
1002+
1003+
# Join channels and exchange messages
1004+
self.joinChannel(1, ch1)
1005+
self.getMessages(1)
1006+
1007+
target_times = []
1008+
1009+
for i in range(3):
1010+
self.sendLine(1, f"PRIVMSG {ch1} :Hello channel {i}")
1011+
ch1_msg = self.getMessages(1)[0]
1012+
target_times.append(ch1_msg.tags["time"])
1013+
time.sleep(0.002)
1014+
1015+
# Now test TARGETS command
1016+
# Get a timestamp before all messages
1017+
before_all = "timestamp=2020-01-01T00:00:00.000Z"
1018+
# Get a timestamp after all messages
1019+
after_all = "timestamp=2262-01-01T00:00:00.000Z"
1020+
1021+
# Execute TARGETS command
1022+
self.sendLine(1, f"CHATHISTORY TARGETS {before_all} {after_all} 100")
1023+
# Verify response
1024+
batch_messages = self.getMessages(1)
1025+
targets_results = self.summarize_chathistory_targets(batch_messages)
1026+
expected_results = [(ch1, target_times[2])]
1027+
self.assertEqual(targets_results, expected_results)
1028+
1029+
# Execute TARGETS command with a time window excluding the latest message
1030+
self.sendLine(
1031+
1, f"CHATHISTORY TARGETS {before_all} timestamp={target_times[1]} 100"
1032+
)
1033+
# Verify response
1034+
batch_messages = self.getMessages(1)
1035+
targets_results = self.summarize_chathistory_targets(batch_messages)
1036+
# should not receive any targets
1037+
self.assertEqual(targets_results, [])
1038+
1039+
def summarize_chathistory_targets(self, batch_messages):
1040+
# Validate batch format
1041+
batch_start = batch_messages[0]
1042+
batch_end = batch_messages[-1]
1043+
self.assertMessageMatch(
1044+
batch_start,
1045+
command="BATCH",
1046+
params=[StrRe(r"\+.*"), "draft/chathistory-targets"],
1047+
)
1048+
batch_tag = batch_start.params[0][1:]
1049+
self.assertMessageMatch(batch_end, command="BATCH", params=["-" + batch_tag])
1050+
# Extract actual targets from the batch
1051+
targets_results = []
1052+
for msg in batch_messages:
1053+
if (
1054+
msg.command == "CHATHISTORY"
1055+
and msg.params[0] == "TARGETS"
1056+
and len(msg.params) >= 3
1057+
):
1058+
targets_results.append((msg.params[1], msg.params[2]))
1059+
return targets_results
1060+
8161061

8171062
assert {f"_validate_chathistory_{cmd}" for cmd in SUBCOMMANDS} == {
8181063
meth_name

0 commit comments

Comments
 (0)