1import ./make-test-python.nix (
2 { pkgs, lib, ... }:
3 {
4 name = "castopod";
5 meta = with lib.maintainers; {
6 maintainers = [ alexoundos ];
7 };
8
9 nodes.castopod =
10 { nodes, ... }:
11 {
12 # otherwise 500 MiB file upload fails!
13 virtualisation.diskSize = 512 + 3 * 512;
14
15 networking.firewall.allowedTCPPorts = [ 80 ];
16 networking.extraHosts = lib.strings.concatStringsSep "\n" (
17 lib.attrsets.mapAttrsToList (
18 name: _: "127.0.0.1 ${name}"
19 ) nodes.castopod.services.nginx.virtualHosts
20 );
21
22 services.castopod = {
23 enable = true;
24 database.createLocally = true;
25 localDomain = "castopod.example.com";
26 maxUploadSize = "512M";
27 };
28 };
29
30 nodes.client =
31 {
32 nodes,
33 pkgs,
34 lib,
35 ...
36 }:
37 let
38 domain = nodes.castopod.services.castopod.localDomain;
39
40 getIP = node: (builtins.head node.networking.interfaces.eth1.ipv4.addresses).address;
41
42 targetPodcastSize = 500 * 1024 * 1024;
43 lameMp3Bitrate = 348300;
44 lameMp3FileAdjust = -800;
45 targetPodcastDuration = toString ((targetPodcastSize + lameMp3FileAdjust) / (lameMp3Bitrate / 8));
46 bannerWidth = 3000;
47 banner = pkgs.runCommand "gen-castopod-cover.jpg" { } ''
48 ${pkgs.imagemagick}/bin/magick `
49 `-background green -bordercolor white -gravity northwest xc:black `
50 `-duplicate 99 `
51 `-seed 1 -resize "%[fx:rand()*72+24]" `
52 `-seed 0 -rotate "%[fx:rand()*360]" -border 6x6 -splice 16x36 `
53 `-seed 0 -rotate "%[fx:floor(rand()*4)*90]" -resize "150x50!" `
54 `+append -crop 10x1@ +repage -roll "+%[fx:(t%2)*72]+0" -append `
55 `-resize ${toString bannerWidth} -quality 1 $out
56 '';
57
58 coverWidth = toString 3000;
59 cover = pkgs.runCommand "gen-castopod-banner.jpg" { } ''
60 ${pkgs.imagemagick}/bin/magick `
61 `-background white -bordercolor white -gravity northwest xc:black `
62 `-duplicate 99 `
63 `-seed 1 -resize "%[fx:rand()*72+24]" `
64 `-seed 0 -rotate "%[fx:rand()*360]" -border 6x6 -splice 36x36 `
65 `-seed 0 -rotate "%[fx:floor(rand()*4)*90]" -resize "144x144!" `
66 `+append -crop 10x1@ +repage -roll "+%[fx:(t%2)*72]+0" -append `
67 `-resize ${coverWidth} -quality 1 $out
68 '';
69 in
70 {
71 networking.extraHosts = lib.strings.concatStringsSep "\n" (
72 lib.attrsets.mapAttrsToList (
73 name: _: "${getIP nodes.castopod} ${name}"
74 ) nodes.castopod.services.nginx.virtualHosts
75 );
76
77 environment.systemPackages =
78 let
79 username = "admin";
80 email = "admin@${domain}";
81 password = "Abcd1234";
82 podcastTitle = "Some Title";
83 episodeTitle = "Episode Title";
84 browser-test =
85 pkgs.writers.writePython3Bin "browser-test"
86 {
87 libraries = [ pkgs.python3Packages.selenium ];
88 flakeIgnore = [
89 "E124"
90 "E501"
91 ];
92 }
93 ''
94 from selenium.webdriver.common.by import By
95 from selenium.webdriver import Firefox
96 from selenium.webdriver.firefox.options import Options
97 from selenium.webdriver.firefox.service import Service
98 from selenium.webdriver.support.ui import WebDriverWait
99 from selenium.webdriver.support import expected_conditions as EC
100 from subprocess import STDOUT
101 import logging
102
103 selenium_logger = logging.getLogger("selenium")
104 selenium_logger.setLevel(logging.DEBUG)
105 selenium_logger.addHandler(logging.StreamHandler())
106
107 options = Options()
108 options.add_argument('--headless')
109 service = Service(log_output=STDOUT)
110 driver = Firefox(options=options, service=service)
111 driver = Firefox(options=options)
112 driver.implicitly_wait(30)
113 driver.set_page_load_timeout(60)
114
115 # install ##########################################################
116
117 driver.get('http://${domain}/cp-install')
118
119 wait = WebDriverWait(driver, 20)
120
121 wait.until(EC.title_contains("installer"))
122
123 driver.find_element(By.CSS_SELECTOR, '#username').send_keys(
124 '${username}'
125 )
126 driver.find_element(By.CSS_SELECTOR, '#email').send_keys(
127 '${email}'
128 )
129 driver.find_element(By.CSS_SELECTOR, '#password').send_keys(
130 '${password}'
131 )
132 driver.find_element(By.XPATH,
133 "//button[contains(., 'Finish install')]"
134 ).click()
135
136 wait.until(EC.title_contains("Auth"))
137
138 driver.find_element(By.CSS_SELECTOR, '#email').send_keys(
139 '${email}'
140 )
141 driver.find_element(By.CSS_SELECTOR, '#password').send_keys(
142 '${password}'
143 )
144 driver.find_element(By.XPATH,
145 "//button[contains(., 'Login')]"
146 ).click()
147
148 wait.until(EC.title_contains("Admin dashboard"))
149
150 # create podcast ###################################################
151
152 driver.get('http://${domain}/admin/podcasts/new')
153
154 wait.until(EC.title_contains("Create podcast"))
155
156 driver.find_element(By.CSS_SELECTOR, '#cover').send_keys(
157 '${cover}'
158 )
159 driver.find_element(By.CSS_SELECTOR, '#banner').send_keys(
160 '${banner}'
161 )
162 driver.find_element(By.CSS_SELECTOR, '#title').send_keys(
163 '${podcastTitle}'
164 )
165 driver.find_element(By.CSS_SELECTOR, '#handle').send_keys(
166 'some_handle'
167 )
168 driver.find_element(By.CSS_SELECTOR, '#description').send_keys(
169 'Some description'
170 )
171 driver.find_element(By.CSS_SELECTOR, '#owner_name').send_keys(
172 'Owner Name'
173 )
174 driver.find_element(By.CSS_SELECTOR, '#owner_email').send_keys(
175 'owner@email.xyz'
176 )
177 driver.find_element(By.XPATH,
178 "//button[contains(., 'Create podcast')]"
179 ).click()
180
181 wait.until(EC.title_contains("${podcastTitle}"))
182
183 driver.find_element(By.XPATH,
184 "//span[contains(., 'Add an episode')]"
185 ).click()
186
187 wait.until(EC.title_contains("Add an episode"))
188
189 # upload podcast ###################################################
190
191 driver.find_element(By.CSS_SELECTOR, '#audio_file').send_keys(
192 '/tmp/podcast.mp3'
193 )
194 driver.find_element(By.CSS_SELECTOR, '#cover').send_keys(
195 '${cover}'
196 )
197 driver.find_element(By.CSS_SELECTOR, '#description').send_keys(
198 'Episode description'
199 )
200 driver.find_element(By.CSS_SELECTOR, '#title').send_keys(
201 '${episodeTitle}'
202 )
203 driver.find_element(By.XPATH,
204 "//button[contains(., 'Create episode')]"
205 ).click()
206
207 wait.until(EC.title_contains("${episodeTitle}"))
208
209 driver.close()
210 driver.quit()
211 '';
212 in
213 [
214 pkgs.firefox-unwrapped
215 pkgs.geckodriver
216 browser-test
217 (pkgs.writeShellApplication {
218 name = "build-mp3";
219 runtimeInputs = with pkgs; [
220 sox
221 lame
222 ];
223 text = ''
224 out=/tmp/podcast.mp3
225 sox -n -r 48000 -t wav - synth ${targetPodcastDuration} sine 440 `
226 `| lame --noreplaygain --cbr -q 9 -b 320 - $out
227 FILESIZE="$(stat -c%s $out)"
228 [ "$FILESIZE" -gt 0 ]
229 [ "$FILESIZE" -le "${toString targetPodcastSize}" ]
230 '';
231 })
232 ];
233 };
234
235 testScript = ''
236 start_all()
237 castopod.wait_for_unit("castopod-setup.service")
238 castopod.wait_for_file("/run/phpfpm/castopod.sock")
239 castopod.wait_for_unit("nginx.service")
240 castopod.wait_for_open_port(80)
241 castopod.wait_until_succeeds("curl -sS -f http://castopod.example.com")
242
243 client.succeed("build-mp3")
244
245 with subtest("Create superadmin, log in, create and upload a podcast"):
246 client.succeed(\
247 "PYTHONUNBUFFERED=1 systemd-cat -t browser-test browser-test")
248 '';
249 }
250)