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