python3Packages.orjson: Disable failing tests on 32 bit
[NixPkgs.git] / nixos / tests / discourse.nix
blob35ca083c6c4e04afb9f9eea90540aa964bd0fe01
1 # This tests Discourse by:
2 #  1. logging in as the admin user
3 #  2. sending a private message to the admin user through the API
4 #  3. replying to that message via email.
6 import ./make-test-python.nix (
7   { pkgs, lib, package ? pkgs.discourse, ... }:
8   let
9     certs = import ./common/acme/server/snakeoil-certs.nix;
10     clientDomain = "client.fake.domain";
11     discourseDomain = certs.domain;
12     adminPassword = "eYAX85qmMJ5GZIHLaXGDAoszD7HSZp5d";
13     secretKeyBase = "381f4ac6d8f5e49d804dae72aa9c046431d2f34c656a705c41cd52fed9b4f6f76f51549f0b55db3b8b0dded7a00d6a381ebe9a4367d2d44f5e743af6628b4d42";
14     admin = {
15       email = "alice@${clientDomain}";
16       username = "alice";
17       fullName = "Alice Admin";
18       passwordFile = "${pkgs.writeText "admin-pass" adminPassword}";
19     };
20   in
21   {
22     name = "discourse";
23     meta = with pkgs.lib.maintainers; {
24       maintainers = [ talyz ];
25     };
27     nodes.discourse =
28       { nodes, ... }:
29       {
30         virtualisation.memorySize = 2048;
31         virtualisation.cores = 4;
32         virtualisation.useNixStoreImage = true;
33         virtualisation.writableStore = false;
35         imports = [ common/user-account.nix ];
37         security.pki.certificateFiles = [
38           certs.ca.cert
39         ];
41         networking.extraHosts = ''
42           127.0.0.1 ${discourseDomain}
43           ${nodes.client.config.networking.primaryIPAddress} ${clientDomain}
44         '';
46         services.postfix = {
47           enableSubmission = true;
48           enableSubmissions = true;
49           submissionsOptions = {
50             smtpd_sasl_auth_enable = "yes";
51             smtpd_client_restrictions = "permit";
52           };
53         };
55         environment.systemPackages = [ pkgs.jq ];
57         services.postgresql.package = pkgs.postgresql_13;
59         services.discourse = {
60           enable = true;
61           inherit admin package;
62           hostname = discourseDomain;
63           sslCertificate = "${certs.${discourseDomain}.cert}";
64           sslCertificateKey = "${certs.${discourseDomain}.key}";
65           secretKeyBaseFile = "${pkgs.writeText "secret-key-base" secretKeyBase}";
66           enableACME = false;
67           mail.outgoing.serverAddress = clientDomain;
68           mail.incoming.enable = true;
69           siteSettings = {
70             posting = {
71               min_post_length = 5;
72               min_first_post_length = 5;
73               min_personal_message_post_length = 5;
74             };
75           };
76           unicornTimeout = 900;
77         };
79         networking.firewall.allowedTCPPorts = [ 25 465 ];
80       };
82     nodes.client =
83       { nodes, ... }:
84       {
85         imports = [ common/user-account.nix ];
87         security.pki.certificateFiles = [
88           certs.ca.cert
89         ];
91         networking.extraHosts = ''
92           127.0.0.1 ${clientDomain}
93           ${nodes.discourse.config.networking.primaryIPAddress} ${discourseDomain}
94         '';
96         services.dovecot2 = {
97           enable = true;
98           protocols = [ "imap" ];
99           modules = [ pkgs.dovecot_pigeonhole ];
100         };
102         services.postfix = {
103           enable = true;
104           origin = clientDomain;
105           relayDomains = [ clientDomain ];
106           config = {
107             compatibility_level = "2";
108             smtpd_banner = "ESMTP server";
109             myhostname = clientDomain;
110             mydestination = clientDomain;
111           };
112         };
114         environment.systemPackages =
115           let
116             replyToEmail = pkgs.writeScriptBin "reply-to-email" ''
117               #!${pkgs.python3.interpreter}
118               import imaplib
119               import smtplib
120               import ssl
121               import email.header
122               from email import message_from_bytes
123               from email.message import EmailMessage
125               with imaplib.IMAP4('localhost') as imap:
126                   imap.login('alice', 'foobar')
127                   imap.select()
128                   status, data = imap.search(None, 'ALL')
129                   assert status == 'OK'
131                   nums = data[0].split()
132                   assert len(nums) == 1
134                   status, msg_data = imap.fetch(nums[0], '(RFC822)')
135                   assert status == 'OK'
137               msg = email.message_from_bytes(msg_data[0][1])
138               subject = str(email.header.make_header(email.header.decode_header(msg['Subject'])))
139               reply_to = email.header.decode_header(msg['Reply-To'])[0][0]
140               message_id = email.header.decode_header(msg['Message-ID'])[0][0]
141               date = email.header.decode_header(msg['Date'])[0][0]
143               ctx = ssl.create_default_context()
144               with smtplib.SMTP_SSL(host='${discourseDomain}', context=ctx) as smtp:
145                   reply = EmailMessage()
146                   reply['Subject'] = 'Re: ' + subject
147                   reply['To'] = reply_to
148                   reply['From'] = 'alice@${clientDomain}'
149                   reply['In-Reply-To'] = message_id
150                   reply['References'] = message_id
151                   reply['Date'] = date
152                   reply.set_content("Test reply.")
154                   smtp.send_message(reply)
155                   smtp.quit()
156             '';
157           in
158             [ replyToEmail ];
160         networking.firewall.allowedTCPPorts = [ 25 ];
161       };
164     testScript = { nodes }:
165       let
166         request = builtins.toJSON {
167           title = "Private message";
168           raw = "This is a test message.";
169           target_usernames = admin.username;
170           archetype = "private_message";
171         };
172       in ''
173         discourse.start()
174         client.start()
176         discourse.wait_for_unit("discourse.service")
177         discourse.wait_for_file("/run/discourse/sockets/unicorn.sock")
178         discourse.wait_until_succeeds("curl -sS -f https://${discourseDomain}")
179         discourse.succeed(
180             "curl -sS -f https://${discourseDomain}/session/csrf -c cookie -b cookie -H 'Accept: application/json' | jq -r '\"X-CSRF-Token: \" + .csrf' > csrf_token",
181             "curl -sS -f https://${discourseDomain}/session -c cookie -b cookie -H @csrf_token -H 'Accept: application/json' -d 'login=${nodes.discourse.config.services.discourse.admin.username}' -d \"password=${adminPassword}\" | jq -e '.user.username == \"${nodes.discourse.config.services.discourse.admin.username}\"'",
182             "curl -sS -f https://${discourseDomain}/login -v -H 'Accept: application/json' -c cookie -b cookie 2>&1 | grep ${nodes.discourse.config.services.discourse.admin.username}",
183         )
185         client.wait_for_unit("postfix.service")
186         client.wait_for_unit("dovecot2.service")
188         discourse.succeed(
189             "sudo -u discourse discourse-rake api_key:create_master[master] >api_key",
190             'curl -sS -f https://${discourseDomain}/posts -X POST -H "Content-Type: application/json" -H "Api-Key: $(<api_key)" -H "Api-Username: system" -d \'${request}\' ',
191         )
193         client.wait_until_succeeds("reply-to-email")
195         discourse.wait_until_succeeds(
196             'curl -sS -f https://${discourseDomain}/topics/private-messages/system -H "Accept: application/json" -H "Api-Key: $(<api_key)" -H "Api-Username: system" | jq -e \'if .topic_list.topics[0].id != null then .topic_list.topics[0].id else null end\' >topic_id'
197         )
198         discourse.succeed(
199             'curl -sS -f https://${discourseDomain}/t/$(<topic_id) -H "Accept: application/json" -H "Api-Key: $(<api_key)" -H "Api-Username: system" | jq -e \'if .post_stream.posts[1].cooked == "<p>Test reply.</p>" then true else null end\' '
200         )
201       '';
202   })