diff --git a/garage.nix b/garage.nix index 2c47ec9..3de3709 100644 --- a/garage.nix +++ b/garage.nix @@ -38,7 +38,8 @@ let ${optionalString corsRules.enable '' garage bucket allow --read --write --owner ${bucketArg} --key tmp - aws --endpoint http://s3.garage.localhost:3900 s3api put-bucket-cors --bucket ${bucketArg} --cors-configuration ${corsRulesJSON} + # TODO: endpoin-url should not be hard-coded + aws --region ${cfg.settings.s3_api.s3_region} --endpoint-url http://s3.garage.localhost:3900 s3api put-bucket-cors --bucket ${bucketArg} --cors-configuration ${corsRulesJSON} garage bucket deny --read --write --owner ${bucketArg} --key tmp ''} ''; @@ -124,21 +125,23 @@ in { }; config = { - virtualisation.diskSize = 2048; - virtualisation.forwardPorts = [ - { - from = "host"; - host.port = 3901; - guest.port = 3901; - } - { - from = "host"; - host.port = 3902; - guest.port = 3902; - } - ]; + virtualisation.vmVariant = { config, ... }: { + virtualisation.diskSize = 2048; + virtualisation.forwardPorts = [ + { + from = "host"; + host.port = 3901; + guest.port = 3901; + } + { + from = "host"; + host.port = 3902; + guest.port = 3902; + } + ]; - environment.systemPackages = [ pkgs.minio-client pkgs.awscli ]; + environment.systemPackages = [ pkgs.minio-client pkgs.awscli ]; + }; networking.firewall.allowedTCPPorts = [ 3901 3902 ]; services.garage = { diff --git a/tests/fediversity.png b/tests/fediversity.png deleted file mode 100644 index 24881fb..0000000 Binary files a/tests/fediversity.png and /dev/null differ diff --git a/tests/green.png b/tests/green.png new file mode 100644 index 0000000..3516109 Binary files /dev/null and b/tests/green.png differ diff --git a/tests/mastodon-garage.nix b/tests/mastodon-garage.nix index c20a771..c423f86 100644 --- a/tests/mastodon-garage.nix +++ b/tests/mastodon-garage.nix @@ -1,7 +1,37 @@ { pkgs, self }: let + lib = pkgs.lib; # python = pkgs.python310.withPackages (ps: with ps; [ requests aiokafka ]); rebuildableTest = import ./rebuildableTest.nix pkgs; + seleniumScript = pkgs.writers.writePython3Bin "selenium-script" + { + libraries = with pkgs.python3Packages; [ selenium ]; + } '' + 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 + + print(1) + + 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) + driver.get("http://mastodon.localhost:55001/public/local") + + # wait until the statuses load + WebDriverWait(driver, 90).until( + lambda x: x.find_element(By.CLASS_NAME, "status")) + + # XXX: how do I save this to the derivation output? + driver.save_screenshot("/mastodon-screenshot.png") + + driver.close() + ''; in rebuildableTest { name = "test-mastodon-garage"; @@ -10,13 +40,29 @@ rebuildableTest { # skipTypeCheck = true; nodes = { - server = { + server = {config, ...}: { + virtualisation.memorySize = lib.mkVMOverride 4096; imports = [ self.nixosModules.garage self.nixosModules.mastodon ]; - environment.systemPackages = with pkgs; [ toot ]; + # TODO: pair down + environment.systemPackages = with pkgs; [ + python3 + firefox-unwrapped + geckodriver + toot + xh + seleniumScript + helix + imagemagick + ]; + environment.variables = { + POST_MEDIA = ./green.png; + AWS_ACCESS_KEY_ID = config.services.garage.ensureKeys.mastodon.id; + AWS_SECRET_ACCESS_KEY = config.services.garage.ensureKeys.mastodon.secret; + }; }; }; - testScript = '' + testScript = {nodes, ...}: '' import re import time @@ -26,33 +72,59 @@ rebuildableTest { 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_re = re.compile('New password: (.*)') - password_match = password_re.match(account_creation_output) + password_match = re.match('.*New password: ([^\n]*).*', account_creation_output, re.S) assert password_match is not None - password = password_match.groups()[0] + password = password_match.group(1) + + with subtest("TTY Login"): + server.wait_until_tty_matches("1", "login: ") + server.send_chars("root\n"); with subtest("Log in with toot"): # 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 - server.send_chars("toot login_ctl\n") - time.sleep(0.2) - # Enter instance URL - server.send_chars("http://mastodon.localhost:55001\n") - time.sleep(0.2) - # Email - server.send_chars("test@test.com\n") - time.sleep(0.2) - # Password + server.send_chars("toot login_cli --instance http://mastodon.localhost:55001 --email test@test.com\n") + server.wait_until_tty_matches("1", "Password: ") server.send_chars(password + "\n") + server.wait_until_tty_matches("1", "Successfully logged in.") - with subtest("Post an image"): - server.succeed("toot post --media ${./fediversity.png}") + with subtest("post text"): + server.succeed("echo 'hello mastodon' | toot post") - # TODO: I don't think there's a good way to test for whether the image visually shows up. - # we can test for CORS headers using curl / xh - # or **maybe** somehow read the javascript console? + with subtest("post image"): + server.succeed("toot post --media $POST_MEDIA") + + with subtest("access garage"): + server.succeed("mc alias set garage http://s3.garage.localhost:3900 --api s3v4 --path off $AWS_ACCESS_KEY_ID $AWS_SECRET_ACCESS_KEY") + server.succeed("mc ls garage/mastodon") + + with subtest("access image in garage"): + image = server.succeed("mc find garage --regex original") + image = image.rstrip() + assert image != "" + server.succeed(f"mc cat {image} >/garage-image.webp") + garage_image_hash = server.succeed("identify -quiet -format '%#' /garage-image.webp") + image_hash = server.succeed("identify -quiet -format '%#' $POST_MEDIA") + assert garage_image_hash == image_hash + + with subtest("Content security policy allows garage images"): + headers = server.succeed("xh -h http://masstodon.localhost:55001/public/local") + csp_match = re.match('^Content-Security-Policy: (.*)$', headers, re.M) + assert csp_match is not None + csp = csp_match.group(1) + # the content security policy should include the garage server + garage_csp = re.match(".*web\.garage\.localhost:3902.*", csp) + assert garage_csp is not None + + with subtest("image displays"): + 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:") + # check that the green image displayed somewhere + re.match(".*#00FF00.*", displayed_colors, re.S) ''; }