1import ./make-test-python.nix ({ pkgs, ... }:
2 let
3 homeserverUrl = "http://homeserver:8448";
4 in
5 {
6 name = "matrix-appservice-irc";
7 meta = {
8 maintainers = pkgs.matrix-appservice-irc.meta.maintainers;
9 };
10
11 nodes = {
12 homeserver = { pkgs, ... }: {
13 # We'll switch to this once the config is copied into place
14 specialisation.running.configuration = {
15 services.matrix-synapse = {
16 enable = true;
17 database_type = "sqlite3";
18 app_service_config_files = [ "/registration.yml" ];
19
20 enable_registration = true;
21
22 listeners = [
23 # The default but tls=false
24 {
25 "bind_address" = "";
26 "port" = 8448;
27 "resources" = [
28 { "compress" = true; "names" = [ "client" ]; }
29 { "compress" = false; "names" = [ "federation" ]; }
30 ];
31 "tls" = false;
32 "type" = "http";
33 "x_forwarded" = false;
34 }
35 ];
36 };
37
38 networking.firewall.allowedTCPPorts = [ 8448 ];
39 };
40 };
41
42 ircd = { pkgs, ... }: {
43 services.ngircd = {
44 enable = true;
45 config = ''
46 [Global]
47 Name = ircd.ircd
48 Info = Server Info Text
49 AdminInfo1 = _
50
51 [Channel]
52 Name = #test
53 Topic = a cool place
54
55 [Options]
56 PAM = no
57 '';
58 };
59 networking.firewall.allowedTCPPorts = [ 6667 ];
60 };
61
62 appservice = { pkgs, ... }: {
63 services.matrix-appservice-irc = {
64 enable = true;
65 registrationUrl = "http://appservice:8009";
66
67 settings = {
68 homeserver.url = homeserverUrl;
69 homeserver.domain = "homeserver";
70
71 ircService.servers."ircd" = {
72 name = "IRCd";
73 port = 6667;
74 dynamicChannels = {
75 enabled = true;
76 aliasTemplate = "#irc_$CHANNEL";
77 };
78 };
79 };
80 };
81
82 networking.firewall.allowedTCPPorts = [ 8009 ];
83 };
84
85 client = { pkgs, ... }: {
86 environment.systemPackages = [
87 (pkgs.writers.writePython3Bin "do_test"
88 {
89 libraries = [ pkgs.python3Packages.matrix-nio ];
90 flakeIgnore = [
91 # We don't live in the dark ages anymore.
92 # Languages like Python that are whitespace heavy will overrun
93 # 79 characters..
94 "E501"
95 ];
96 } ''
97 import sys
98 import socket
99 import functools
100 from time import sleep
101 import asyncio
102
103 from nio import AsyncClient, RoomMessageText, JoinResponse
104
105
106 async def matrix_room_message_text_callback(matrix: AsyncClient, msg: str, _r, e):
107 print("Received matrix text message: ", e)
108 if msg in e.body:
109 print("Received hi from IRC")
110 await matrix.close()
111 exit(0) # Actual exit point
112
113
114 class IRC:
115 def __init__(self):
116 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
117 sock.connect(("ircd", 6667))
118 sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
119 sock.send(b"USER bob bob bob :bob\n")
120 sock.send(b"NICK bob\n")
121 self.sock = sock
122
123 def join(self, room: str):
124 self.sock.send(f"JOIN {room}\n".encode())
125
126 def privmsg(self, room: str, msg: str):
127 self.sock.send(f"PRIVMSG {room} :{msg}\n".encode())
128
129 def expect_msg(self, body: str):
130 buffer = ""
131 while True:
132 buf = self.sock.recv(1024).decode()
133 buffer += buf
134 if body in buffer:
135 return
136
137
138 async def run(homeserver: str):
139 irc = IRC()
140
141 matrix = AsyncClient(homeserver)
142 response = await matrix.register("alice", "foobar")
143 print("Matrix register response: ", response)
144
145 response = await matrix.join("#irc_#test:homeserver")
146 print("Matrix join room response:", response)
147 assert isinstance(response, JoinResponse)
148 room_id = response.room_id
149
150 irc.join("#test")
151 # FIXME: what are we waiting on here? Matrix? IRC? Both?
152 # 10s seem bad for busy hydra machines.
153 sleep(10)
154
155 # Exchange messages
156 print("Sending text message to matrix room")
157 response = await matrix.room_send(
158 room_id=room_id,
159 message_type="m.room.message",
160 content={"msgtype": "m.text", "body": "hi from matrix"},
161 )
162 print("Matrix room send response: ", response)
163 irc.privmsg("#test", "hi from irc")
164
165 print("Waiting for the matrix message to appear on the IRC side...")
166 irc.expect_msg("hi from matrix")
167
168 callback = functools.partial(
169 matrix_room_message_text_callback, matrix, "hi from irc"
170 )
171 matrix.add_event_callback(callback, RoomMessageText)
172
173 print("Waiting for matrix message...")
174 await matrix.sync_forever()
175
176 exit(1) # Unreachable
177
178
179 if __name__ == "__main__":
180 asyncio.run(run(sys.argv[1]))
181 ''
182 )
183 ];
184 };
185 };
186
187 testScript = ''
188 import pathlib
189
190 start_all()
191
192 ircd.wait_for_unit("ngircd.service")
193 ircd.wait_for_open_port(6667)
194
195 with subtest("start the appservice"):
196 appservice.wait_for_unit("matrix-appservice-irc.service")
197 appservice.wait_for_open_port(8009)
198
199 with subtest("copy the registration file"):
200 appservice.copy_from_vm("/var/lib/matrix-appservice-irc/registration.yml")
201 homeserver.copy_from_host(
202 pathlib.Path(os.environ.get("out", os.getcwd())) / "registration.yml", "/"
203 )
204 homeserver.succeed("chmod 444 /registration.yml")
205
206 with subtest("start the homeserver"):
207 homeserver.succeed(
208 "/run/current-system/specialisation/running/bin/switch-to-configuration test >&2"
209 )
210
211 homeserver.wait_for_unit("matrix-synapse.service")
212 homeserver.wait_for_open_port(8448)
213
214 with subtest("ensure messages can be exchanged"):
215 client.succeed("do_test ${homeserverUrl} >&2")
216 '';
217 })