Fediversity/services/tests/mastodon.nix

163 lines
6.2 KiB
Nix
Raw Normal View History

2024-11-27 12:33:49 +01:00
## This file is a basic test of Mastodon functionalities.
##
## NOTE: This test will fail for Mastodon < 4.3 because of
## https://github.com/mastodon/mastodon/issues/31145
{ pkgs, self }:
2024-11-27 12:33:49 +01:00
let
2024-07-18 12:44:13 +02:00
lib = pkgs.lib;
2024-11-11 17:28:35 +01:00
## FIXME: this binding was not used, but maybe we want a side-effect or something?
# rebuildableTest = import ./rebuildableTest.nix pkgs;
2024-11-27 12:33:33 +01:00
testImage = pkgs.copyPathToStore ./green.png;
testImageColour = "#00FF00";
2024-11-11 17:25:42 +01:00
seleniumScript =
pkgs.writers.writePython3Bin "selenium-script"
{ libraries = with pkgs.python3Packages; [ selenium ]; }
2024-11-11 17:25:42 +01:00
''
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.firefox.options import Options
from selenium.webdriver.support.ui import WebDriverWait
options = Options()
options.add_argument("--headless")
# devtools don't show up in headless screenshots
# options.add_argument("-devtools")
service = webdriver.FirefoxService(executable_path="${lib.getExe pkgs.geckodriver}") # noqa: E501
driver = webdriver.Firefox(options=options, service=service)
2024-11-27 12:33:33 +01:00
driver.get("http://mastodon.localhost/public/local")
2024-11-11 17:25:42 +01:00
# wait until the statuses load
WebDriverWait(driver, 90).until(
lambda x: x.find_element(By.CLASS_NAME, "status"))
driver.save_screenshot("/mastodon-screenshot.png")
driver.close()
'';
in
2024-11-27 12:33:49 +01:00
pkgs.nixosTest {
name = "test-mastodon-garage";
nodes = {
2024-11-11 17:25:42 +01:00
server =
{ config, ... }:
{
virtualisation.memorySize = lib.mkVMOverride 4096;
imports = with self.nixosModules; [
fediversity
2024-11-14 01:10:00 +01:00
../vm/garage-vm.nix
../vm/mastodon-vm.nix
2024-11-27 12:33:33 +01:00
../vm/interactive-vm.nix
2024-11-11 17:25:42 +01:00
];
# TODO: pair down
environment.systemPackages = with pkgs; [
python3
firefox-unwrapped
geckodriver
toot
xh
seleniumScript
helix
imagemagick
2024-11-27 12:33:33 +01:00
expect
2024-11-11 17:25:42 +01:00
];
environment.variables = {
AWS_ACCESS_KEY_ID = config.services.garage.ensureKeys.mastodon.id;
AWS_SECRET_ACCESS_KEY = config.services.garage.ensureKeys.mastodon.secret;
};
2024-07-18 12:44:13 +02:00
};
};
2024-11-11 17:25:42 +01:00
testScript =
{ nodes, ... }:
''
import re
import time
server.start()
with subtest("Mastodon starts"):
server.wait_for_unit("mastodon-web.service")
# make sure mastodon is fully up and running before we interact with it
# TODO: is there a way to test for this?
time.sleep(180)
with subtest("Account creation"):
account_creation_output = server.succeed("mastodon-tootctl accounts create test --email test@test.com --confirmed --approve")
password_match = re.match('.*New password: ([^\n]*).*', account_creation_output, re.S)
if password_match is None:
raise Exception(f"account creation did not generate a password.\n{account_creation_output}")
password = password_match.group(1)
2024-11-27 12:33:33 +01:00
# print(f"Test user (test@test.com)'s password is: {password}")
2024-11-11 17:25:42 +01:00
with subtest("Log in with toot"):
2024-11-27 12:33:33 +01:00
# toot doesn't provide a way to just specify our login details as
# arguments, so we have to pretend we're typing them in at the prompt;
# we use 'expect' for this purpose.
server.succeed(f"""
expect -c '
spawn toot login_cli --instance http://mastodon.localhost:55001 --email test@test.com
expect "Password: "
send "{password}\\n"
interact
' >&2
""")
with subtest("Post a text"):
2024-11-11 17:25:42 +01:00
server.succeed("echo 'hello mastodon' | toot post")
2024-11-27 12:33:33 +01:00
with subtest("Post an image"):
server.succeed("toot post --media ${testImage}")
2024-11-11 17:25:42 +01:00
2024-11-27 12:33:33 +01:00
with subtest("Access garage"):
2024-11-11 17:25:42 +01:00
server.succeed("mc alias set garage ${nodes.server.fediversity.internal.garage.api.url} --api s3v4 --path off $AWS_ACCESS_KEY_ID $AWS_SECRET_ACCESS_KEY")
server.succeed("mc ls garage/mastodon")
2024-11-27 12:33:33 +01:00
with subtest("Access image in garage"):
2024-11-11 17:25:42 +01:00
image = server.succeed("mc find garage --regex original")
image = image.rstrip()
if image == "":
raise Exception("image posted to mastodon did not get stored in garage")
server.succeed(f"mc cat {image} >/garage-image.webp")
garage_image_hash = server.succeed("identify -quiet -format '%#' /garage-image.webp")
2024-11-27 12:33:33 +01:00
image_hash = server.succeed("identify -quiet -format '%#' ${testImage}")
2024-11-11 17:25:42 +01:00
if garage_image_hash != image_hash:
raise Exception("image stored in garage did not match image uploaded")
2024-11-27 12:33:33 +01:00
with subtest("Content-Security-Policy allows garage content"):
2024-11-11 17:25:42 +01:00
headers = server.succeed("xh -h http://mastodon.localhost:55001/public/local")
csp_match = None
# I can't figure out re.MULTILINE
for header in headers.split("\n"):
2024-11-27 12:33:33 +01:00
csp_match = re.match('^Content-Security-Policy: (.*)$', header)
2024-11-11 17:25:42 +01:00
if csp_match is not None:
break
if csp_match is None:
raise Exception("mastodon did not send a content security policy header")
csp = csp_match.group(1)
2024-11-27 12:33:33 +01:00
# the connect-src content security policy should include the garage server
2024-11-11 17:25:42 +01:00
## TODO: use `nodes.server.fediversity.internal.garage.api.url` same as above, but beware of escaping the regex. Be careful with port 80 though.
garage_csp = re.match(".*; img-src[^;]*web\.garage\.localhost.*", csp)
if garage_csp is None:
2024-11-27 12:33:33 +01:00
raise Exception("Mastodon's Content-Security-Policy does not include Garage.")
2024-11-11 17:25:42 +01:00
2024-11-27 12:33:33 +01:00
# this could in theory give a false positive if mastodon changes it's colorscheme to include ${testImageColour}.
with subtest("Image displays"):
2024-11-11 17:25:42 +01:00
server.succeed("selenium-script")
server.copy_from_vm("/mastodon-screenshot.png", "")
displayed_colors = server.succeed("convert /mastodon-screenshot.png -define histogram:unique-colors=true -format %c histogram:info:")
2024-11-27 12:33:33 +01:00
# check that the image displayed somewhere
image_check = re.match(".*${testImageColour}.*", displayed_colors, re.S)
if image_check is None:
2024-11-11 17:25:42 +01:00
raise Exception("cannot detect the uploaded image on mastodon page.")
'';
}