forked from fediversity/simple-nixos-fediverse
		
	WIP
This commit is contained in:
		
							parent
							
								
									5fd5c37834
								
							
						
					
					
						commit
						b9cf2d5e10
					
				
					 5 changed files with 198 additions and 12 deletions
				
			
		|  | @ -60,12 +60,12 @@ NOTE: it sometimes takes a while for the services to start up, and in the meanti | |||
| ## NixOS Tests | ||||
| 
 | ||||
| Tests live in the aptly named `tests/` directory, and can be accessed at the flake URI `.#checks.<system>.<test-name>` e.g. `nix build .#checks.x86_64-linux.mastodon-garage`. | ||||
| They can also be run interactively with  | ||||
| They can also be run interactively with | ||||
| ``` | ||||
| nix build .#checks.<system>.<test>.driverInteractive | ||||
| ./result/bin/nixos-test-driver 2>output | ||||
| ```` | ||||
| you can `less -F output` from a different terminal to follow along. | ||||
| you can `less output` and then `F` from a different terminal to follow along. | ||||
| 
 | ||||
| These tests are also equiped with the same port forwarding as the VMs, so when running interactively you should be able to access services through a browser running on your machine. | ||||
| 
 | ||||
|  | @ -105,5 +105,3 @@ When mastodon is running in production mode, we have a few problems: | |||
| - mastodon is trying to fetch `custom.css` from https://mastodon.localhost (no port), which is not the configured `LOCAL_DOMAIN`, so it's unclear why. | ||||
| 
 | ||||
| NixOS tests do not take the configuration from `virtualisation.vmVariant`. This seems like an oversight since people don't tend to mix normal NixOS configurations with the ones they're using for tests. This should be pretty easy to rectify upstream. | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										16
									
								
								flake.lock
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										16
									
								
								flake.lock
									
										
									
										generated
									
									
									
								
							|  | @ -2,14 +2,18 @@ | |||
|   "nodes": { | ||||
|     "nixpkgs": { | ||||
|       "locked": { | ||||
|         "lastModified": 1724846166, | ||||
|         "narHash": "sha256-Um1Ahz09XHepSA1QQmdQk8nbsJEwHe54gP3naWp6D94=", | ||||
|         "path": "/home/qolen/nixpkgs", | ||||
|         "type": "path" | ||||
|         "lastModified": 1723726852, | ||||
|         "narHash": "sha256-lRzlx4fPRtzA+dgz9Rh4WK5yAW3TsAXx335DQqxY2XY=", | ||||
|         "owner": "radvendii", | ||||
|         "repo": "nixpkgs", | ||||
|         "rev": "9286249a1673cf5b14a4793e22dd44b70cb69a0d", | ||||
|         "type": "github" | ||||
|       }, | ||||
|       "original": { | ||||
|         "path": "/home/qolen/nixpkgs", | ||||
|         "type": "path" | ||||
|         "owner": "radvendii", | ||||
|         "ref": "nixos_rebuild_tests", | ||||
|         "repo": "nixpkgs", | ||||
|         "type": "github" | ||||
|       } | ||||
|     }, | ||||
|     "root": { | ||||
|  |  | |||
|  | @ -52,6 +52,7 @@ | |||
| 
 | ||||
|     checks.${system} = { | ||||
|       mastodon-garage = import ./tests/mastodon-garage.nix { inherit pkgs self; }; | ||||
|       pixelfed-garage = import ./tests/pixelfed-garage.nix { inherit pkgs self; }; | ||||
|     }; | ||||
| 
 | ||||
|     devShells.${system}.default = pkgs.mkShell { | ||||
|  |  | |||
|  | @ -26,7 +26,6 @@ let | |||
|     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() | ||||
|  | @ -58,7 +57,7 @@ pkgs.nixosTest { | |||
|     }; | ||||
|   }; | ||||
| 
 | ||||
|   testScript = {nodes, ...}: '' | ||||
|   testScript = '' | ||||
|     import re | ||||
|     import time | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										184
									
								
								tests/pixelfed-garage.nix
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										184
									
								
								tests/pixelfed-garage.nix
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,184 @@ | |||
| { pkgs, self }: | ||||
| let | ||||
|   lib = pkgs.lib; | ||||
|   rebuildableTest = import ./rebuildableTest.nix pkgs; | ||||
|   seleniumScript = pkgs.writers.writePython3Bin "selenium-script" | ||||
|     { | ||||
|       libraries = with pkgs.python3Packages; [ selenium ]; | ||||
|     } '' | ||||
|     import sys | ||||
|     from selenium import webdriver | ||||
|     from selenium.webdriver.common.by import By | ||||
|     from selenium.webdriver.support.wait import WebDriverWait | ||||
|     from selenium.webdriver.chrome.options import Options | ||||
| 
 | ||||
|     email = sys.argv[1] | ||||
|     password = sys.argv[2] | ||||
| 
 | ||||
|     green_path = "${./green.png}" | ||||
|     screenshot_path = "/screenshot.png" | ||||
| 
 | ||||
|     # Create and configure driver. It is important to set the window size such that | ||||
|     # the “Create New Post” button is visible. | ||||
|     print("Create and configure driver...") | ||||
|     options = Options() | ||||
|     options.add_argument("--headless=new") | ||||
|     service = webdriver.ChromeService(executable_path="${lib.getExe pkgs.chromedriver}")  # noqa: E501 | ||||
|     driver = webdriver.Chrome(options=options, service=service) | ||||
|     driver.implicitly_wait(10) | ||||
|     driver.set_window_size(1920, 1200) | ||||
| 
 | ||||
|     # FIXME: Replace the By.XPATH by By.CSS_SELECTOR. | ||||
| 
 | ||||
|     # Go to Pixelfed and login. | ||||
|     print("Open login page...") | ||||
|     driver.get("http://pixelfed.localhost/login") | ||||
|     print("Enter email...") | ||||
|     driver.find_element(By.ID, "email").send_keys(email) | ||||
|     print("Enter password...") | ||||
|     driver.find_element(By.ID, "password").send_keys(password) | ||||
|     # FIXME: This is disgusting. Find instead the input type submit in the form | ||||
|     # with action ending in "/login". | ||||
|     print("Click “Login” button...") | ||||
|     driver.find_element(By.XPATH, "//button[normalize-space()='Login']").click() | ||||
| 
 | ||||
|     # Find the new post form, fill it in with our pictureand a caption. | ||||
|     print("Click on “Create New Post”...") | ||||
|     driver.find_element(By.LINK_TEXT, "Create New Post").click() | ||||
|     print("Add file to input element...") | ||||
|     driver.find_element(By.XPATH, "//input[@type='file']").send_keys(green_path) | ||||
|     print("Click on “Post” button...") | ||||
|     driver.find_element(By.LINK_TEXT, "Post").click() | ||||
| 
 | ||||
|     # Wait until the post loads, and in particular its picture, then take a | ||||
|     # screenshot of the whole page. | ||||
|     print("Wait for post and image to be loaded...") | ||||
|     WebDriverWait(driver, timeout=10).until( | ||||
|         lambda d: d.execute_script( | ||||
|             "return arguments[0].complete", | ||||
|             d.find_element( | ||||
|                 By.XPATH, "//div[@class='timeline-status-component-content']//img" | ||||
|             ), | ||||
|         ) | ||||
|     ) | ||||
|     print("Take screenshot...") | ||||
|     driver.save_screenshot(screenshot_path) | ||||
| 
 | ||||
|     # All done ^-^ | ||||
|     driver.quit() | ||||
|   ''; | ||||
| in | ||||
| pkgs.nixosTest { | ||||
|   name = "test-pixelfed-garage"; | ||||
| 
 | ||||
|   nodes = { | ||||
|     server = { config, ... }: { | ||||
|       virtualisation = { | ||||
|         memorySize = lib.mkVMOverride 8192; | ||||
|         cores = 8; | ||||
|       }; | ||||
|       imports = with self.nixosModules; [ garage pixelfed pixelfed-vm ]; | ||||
|       # TODO: pair down | ||||
|       environment.systemPackages = with pkgs; [ | ||||
|         python3 | ||||
|         chromium | ||||
|         xh | ||||
|         seleniumScript | ||||
|         helix | ||||
|         imagemagick | ||||
|       ]; | ||||
|       environment.variables = { | ||||
|         POST_MEDIA = ./green.png; | ||||
|         AWS_ACCESS_KEY_ID = config.services.garage.ensureKeys.pixelfed.id; | ||||
|         AWS_SECRET_ACCESS_KEY = config.services.garage.ensureKeys.pixelfed.secret; | ||||
|       }; | ||||
|     }; | ||||
|   }; | ||||
| 
 | ||||
|   testScript = '' | ||||
|     import re | ||||
|     # import time | ||||
| 
 | ||||
|     server.start() | ||||
| 
 | ||||
|     with subtest("Pixelfed starts"): | ||||
|       server.wait_for_unit("phpfpm-pixelfed.service") | ||||
| 
 | ||||
|     # make sure pixelfed is fully up and running before we interact with it | ||||
|     # TODO: is there a way to test for this? | ||||
|     # REVIEW: this is copied from the Mastodon test; Pixelfed might be faster to start up. | ||||
|     # time.sleep(180) | ||||
| 
 | ||||
|     # # REVIEW: not sure why necessary. if it is, could we push this into the installation? | ||||
|     # # FIXME: this is an interactive command; look into flags to make it non-interactive | ||||
|     # with subtest("Passport install"): | ||||
|     #   server.succeed("pixelfed-manage passport:install") | ||||
| 
 | ||||
|     with subtest("Account creation"): | ||||
|       password = "testtest" | ||||
|       server.succeed(f"pixelfed-manage user:create --name=test --username=test --email=test@test.com --password={password} --confirm_email=1") | ||||
| 
 | ||||
|     # # REVIEW: do we actually need TTY? | ||||
|     # 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_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 text"): | ||||
|     #   server.succeed("echo 'hello mastodon' | toot post") | ||||
| 
 | ||||
|     # 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() | ||||
|     #   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") | ||||
|     #   image_hash = server.succeed("identify -quiet -format '%#' $POST_MEDIA") | ||||
|     #   if garage_image_hash != image_hash: | ||||
|     #     raise Exception("image stored in garage did not match image uploaded") | ||||
| 
 | ||||
|     # with subtest("Content security policy allows garage images"): | ||||
|     #   headers = server.succeed("xh -h http://masstodon.localhost:55001/public/local") | ||||
|     #   csp_match = None | ||||
|     #   # I can't figure out re.MULTILINE | ||||
|     #   for header in headers.split("\n"): | ||||
|     #     csp_match =  re.match('^Content-Security-Policy: (.*)$', header) | ||||
|     #     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) | ||||
|     #   # the img-src content security policy should include the garage server | ||||
|     #   garage_csp = re.match(".*; img-src[^;]*web\.garage\.localhost:3902.*", csp) | ||||
|     #   if garage_csp is None: | ||||
|     #     raise Exception("Mastodon's content security policy does not include garage server. image will not be displayed properly on mastodon.") | ||||
| 
 | ||||
|     # NOTE: This could in theory give a false positive if pixelfed changes it's | ||||
|     # colorscheme to include pure green. (see same problem in mastodon-garage.nix). | ||||
|     # TODO: For instance: post a red image and check that the green pixel IS NOT | ||||
|     # there, then post a green image and check that the green pixel IS there. | ||||
| 
 | ||||
|     with subtest("image displays"): | ||||
|       server.succeed(f"selenium-script test@test.com {password}") | ||||
|       server.copy_from_vm("/screenshot.png", "") | ||||
|       displayed_colors = server.succeed("convert /screenshot.png -define histogram:unique-colors=true -format %c histogram:info:") | ||||
|       # check that the green image displayed somewhere | ||||
|       green_check = re.match(".*#00FF00.*", displayed_colors, re.S) | ||||
|       if green_check is None: | ||||
|         raise Exception("cannot detect the uploaded image on pixelfed page.") | ||||
|   ''; | ||||
| } | ||||
		Reference in a new issue