1import ../make-test-python.nix (
2 { pkgs, ... }:
3 let
4 homeserverDomain = "server";
5 homeserverUrl = "http://server:8008";
6 username = "alice";
7 instagramBotUsername = "instagrambot";
8 facebookBotUsername = "facebookbot";
9 in
10 {
11 name = "mautrix-meta-sqlite";
12 meta.maintainers = pkgs.mautrix-meta.meta.maintainers;
13
14 nodes = {
15 server =
16 { config, pkgs, ... }:
17 {
18 services.matrix-synapse = {
19 enable = true;
20 settings = {
21 database.name = "sqlite3";
22
23 enable_registration = true;
24
25 # don't use this in production, always use some form of verification
26 enable_registration_without_verification = true;
27
28 listeners = [
29 {
30 # The default but tls=false
31 bind_addresses = [
32 "0.0.0.0"
33 ];
34 port = 8008;
35 resources = [
36 {
37 "compress" = true;
38 "names" = [ "client" ];
39 }
40 {
41 "compress" = false;
42 "names" = [ "federation" ];
43 }
44 ];
45 tls = false;
46 type = "http";
47 }
48 ];
49 };
50 };
51
52 services.mautrix-meta.instances.facebook = {
53 enable = true;
54
55 settings = {
56 homeserver = {
57 address = homeserverUrl;
58 domain = homeserverDomain;
59 };
60
61 appservice = {
62 port = 8009;
63
64 bot.username = facebookBotUsername;
65 };
66
67 bridge.permissions."@${username}:server" = "user";
68 };
69 };
70
71 services.mautrix-meta.instances.instagram = {
72 enable = true;
73
74 settings = {
75 homeserver = {
76 address = homeserverUrl;
77 domain = homeserverDomain;
78 };
79
80 appservice = {
81 port = 8010;
82
83 bot.username = instagramBotUsername;
84 };
85
86 bridge.permissions."@${username}:server" = "user";
87 };
88 };
89
90 networking.firewall.allowedTCPPorts = [ 8008 ];
91 };
92
93 client =
94 { pkgs, ... }:
95 {
96 environment.systemPackages = [
97 (pkgs.writers.writePython3Bin "register_user"
98 {
99 libraries = [ pkgs.python3Packages.matrix-nio ];
100 flakeIgnore = [
101 # We don't live in the dark ages anymore.
102 # Languages like Python that are whitespace heavy will overrun
103 # 79 characters..
104 "E501"
105 ];
106 }
107 ''
108 import sys
109 import asyncio
110
111 from nio import AsyncClient
112
113
114 async def run(username: str, homeserver: str):
115 matrix = AsyncClient(homeserver)
116
117 response = await matrix.register(username, "foobar")
118 print("Matrix register response: ", response)
119
120
121 if __name__ == "__main__":
122 asyncio.run(run(sys.argv[1], sys.argv[2]))
123 ''
124 )
125 (pkgs.writers.writePython3Bin "do_test"
126 {
127 libraries = [ pkgs.python3Packages.matrix-nio ];
128 flakeIgnore = [
129 # We don't live in the dark ages anymore.
130 # Languages like Python that are whitespace heavy will overrun
131 # 79 characters..
132 "E501"
133 ];
134 }
135 ''
136 import sys
137 import functools
138 import asyncio
139
140 from nio import AsyncClient, RoomMessageNotice, RoomCreateResponse, RoomInviteResponse
141
142
143 async def message_callback(matrix: AsyncClient, msg: str, _r, e):
144 print("Received matrix text message: ", e)
145 assert msg in e.body
146 exit(0) # Success!
147
148
149 async def run(username: str, bot_username: str, homeserver: str):
150 matrix = AsyncClient(homeserver, f"@{username}:${homeserverDomain}")
151
152 response = await matrix.login("foobar")
153 print("Matrix login response: ", response)
154
155 # Open a DM with the bridge bot
156 response = await matrix.room_create()
157 print("Matrix create room response:", response)
158 assert isinstance(response, RoomCreateResponse)
159 room_id = response.room_id
160
161 response = await matrix.room_invite(room_id, f"@{bot_username}:${homeserverDomain}")
162 assert isinstance(response, RoomInviteResponse)
163
164 callback = functools.partial(
165 message_callback, matrix, "Hello, I'm an Instagram bridge bot."
166 )
167 matrix.add_event_callback(callback, RoomMessageNotice)
168
169 print("Waiting for matrix message...")
170 await matrix.sync_forever(timeout=30000)
171
172
173 if __name__ == "__main__":
174 asyncio.run(run(sys.argv[1], sys.argv[2], sys.argv[3]))
175 ''
176 )
177 ];
178 };
179 };
180
181 testScript = ''
182 def extract_token(data):
183 stdout = data[1]
184 stdout = stdout.strip()
185 line = stdout.split('\n')[-1]
186 return line.split(':')[-1].strip("\" '\n")
187
188 def get_token_from(token, file):
189 data = server.execute(f"cat {file} | grep {token}")
190 return extract_token(data)
191
192 def get_as_token_from(file):
193 return get_token_from("as_token", file)
194
195 def get_hs_token_from(file):
196 return get_token_from("hs_token", file)
197
198 config_yaml = "/var/lib/mautrix-meta-facebook/config.yaml"
199 registration_yaml = "/var/lib/mautrix-meta-facebook/meta-registration.yaml"
200
201 start_all()
202
203 with subtest("wait for bridges and homeserver"):
204 # bridge
205 server.wait_for_unit("mautrix-meta-facebook.service")
206 server.wait_for_unit("mautrix-meta-instagram.service")
207
208 # homeserver
209 server.wait_for_unit("matrix-synapse.service")
210
211 server.wait_for_open_port(8008)
212 # Bridges only open the port after they contact the homeserver
213 server.wait_for_open_port(8009)
214 server.wait_for_open_port(8010)
215
216 with subtest("register user"):
217 client.succeed("register_user ${username} ${homeserverUrl} >&2")
218
219 with subtest("ensure messages can be exchanged"):
220 client.succeed("do_test ${username} ${facebookBotUsername} ${homeserverUrl} >&2")
221 client.succeed("do_test ${username} ${instagramBotUsername} ${homeserverUrl} >&2")
222
223 with subtest("ensure as_token and hs_token stays same after restart"):
224 generated_as_token_facebook = get_as_token_from(config_yaml)
225 generated_hs_token_facebook = get_hs_token_from(config_yaml)
226
227 generated_as_token_facebook_registration = get_as_token_from(registration_yaml)
228 generated_hs_token_facebook_registration = get_hs_token_from(registration_yaml)
229
230 # Indirectly checks the as token is not set to something like empty string or "null"
231 assert len(generated_as_token_facebook) > 20, f"as_token ({generated_as_token_facebook}) is too short, something went wrong"
232 assert len(generated_hs_token_facebook) > 20, f"hs_token ({generated_hs_token_facebook}) is too short, something went wrong"
233
234 assert generated_as_token_facebook == generated_as_token_facebook_registration, f"as_token should be the same in registration ({generated_as_token_facebook_registration}) and configuration ({generated_as_token_facebook}) files"
235 assert generated_hs_token_facebook == generated_hs_token_facebook_registration, f"hs_token should be the same in registration ({generated_hs_token_facebook_registration}) and configuration ({generated_hs_token_facebook}) files"
236
237 server.systemctl("restart mautrix-meta-facebook")
238 server.systemctl("restart mautrix-meta-instagram")
239
240 server.wait_for_open_port(8009)
241 server.wait_for_open_port(8010)
242
243 new_as_token_facebook = get_as_token_from(config_yaml)
244 new_hs_token_facebook = get_hs_token_from(config_yaml)
245
246 assert generated_as_token_facebook == new_as_token_facebook, f"as_token should stay the same after restart inside the configuration file (is: {new_as_token_facebook}, was: {generated_as_token_facebook})"
247 assert generated_hs_token_facebook == new_hs_token_facebook, f"hs_token should stay the same after restart inside the configuration file (is: {new_hs_token_facebook}, was: {generated_hs_token_facebook})"
248
249 new_as_token_facebook = get_as_token_from(registration_yaml)
250 new_hs_token_facebook = get_hs_token_from(registration_yaml)
251
252 assert generated_as_token_facebook == new_as_token_facebook, f"as_token should stay the same after restart inside the registration file (is: {new_as_token_facebook}, was: {generated_as_token_facebook})"
253 assert generated_hs_token_facebook == new_hs_token_facebook, f"hs_token should stay the same after restart inside the registration file (is: {new_hs_token_facebook}, was: {generated_hs_token_facebook})"
254
255 with subtest("ensure messages can be exchanged after restart"):
256 client.succeed("do_test ${username} ${instagramBotUsername} ${homeserverUrl} >&2")
257 client.succeed("do_test ${username} ${facebookBotUsername} ${homeserverUrl} >&2")
258 '';
259 }
260)