at 23.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 { 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 }; 26 27 nodes.discourse = 28 { nodes, ... }: 29 { 30 virtualisation.memorySize = 2048; 31 virtualisation.cores = 4; 32 virtualisation.useNixStoreImage = true; 33 virtualisation.writableStore = false; 34 35 imports = [ common/user-account.nix ]; 36 37 security.pki.certificateFiles = [ 38 certs.ca.cert 39 ]; 40 41 networking.extraHosts = '' 42 127.0.0.1 ${discourseDomain} 43 ${nodes.client.networking.primaryIPAddress} ${clientDomain} 44 ''; 45 46 services.postfix = { 47 enableSubmission = true; 48 enableSubmissions = true; 49 submissionsOptions = { 50 smtpd_sasl_auth_enable = "yes"; 51 smtpd_client_restrictions = "permit"; 52 }; 53 }; 54 55 environment.systemPackages = [ pkgs.jq ]; 56 57 services.postgresql.package = pkgs.postgresql_13; 58 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 }; 78 79 networking.firewall.allowedTCPPorts = [ 25 465 ]; 80 }; 81 82 nodes.client = 83 { nodes, ... }: 84 { 85 imports = [ common/user-account.nix ]; 86 87 security.pki.certificateFiles = [ 88 certs.ca.cert 89 ]; 90 91 networking.extraHosts = '' 92 127.0.0.1 ${clientDomain} 93 ${nodes.discourse.networking.primaryIPAddress} ${discourseDomain} 94 ''; 95 96 services.dovecot2 = { 97 enable = true; 98 protocols = [ "imap" ]; 99 modules = [ pkgs.dovecot_pigeonhole ]; 100 }; 101 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 }; 113 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 124 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' 130 131 nums = data[0].split() 132 assert len(nums) == 1 133 134 status, msg_data = imap.fetch(nums[0], '(RFC822)') 135 assert status == 'OK' 136 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] 142 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.") 153 154 smtp.send_message(reply) 155 smtp.quit() 156 ''; 157 in 158 [ replyToEmail ]; 159 160 networking.firewall.allowedTCPPorts = [ 25 ]; 161 }; 162 163 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() 175 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.services.discourse.admin.username}' -d \"password=${adminPassword}\" | jq -e '.user.username == \"${nodes.discourse.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.services.discourse.admin.username}", 183 ) 184 185 client.wait_for_unit("postfix.service") 186 client.wait_for_unit("dovecot2.service") 187 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 ) 192 193 client.wait_until_succeeds("reply-to-email") 194 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 })