import pytest import tempfile import subprocess import random import os import time import requests # Generic usefule fixtures @pytest.fixture def temp(): """Write data to a temporary file and return the handle This is a fixture instead of a utility function so that temporary files have a test-scoped lifetime """ handles = [] def fun(data): handle = tempfile.NamedTemporaryFile(delete=False) if type(data) is str: data = data.encode("utf8") handle.write(data) handle.flush() handles.append(handle) return handle.name yield fun del handles # Useless but explicit # Test application instance @pytest.fixture(scope="module") def app(username, password): """Run an isolated application instance and return the URL""" data = tempfile.TemporaryDirectory() port = 5000 + random.randint(1, 999) # Port = 5XXX url = f"http://localhost:{port}" env = os.environ env.update(FLASK_APP="hiboo", SQLALCHEMY_DATABASE_URI=f"sqlite:///{data.name}/hiboo.db") subprocess.run(["flask", "db", "upgrade"], env=env) subprocess.run(["flask", "user", "create", username, password], env=env) subprocess.run(["flask", "user", "promote", username], env=env) proc = subprocess.Popen(["flask", "run", "-p", str(port)], env=env) # Wait for the server to be ready for _ in range(30): try: assert requests.get(url).status_code == 200 except Exception as e: print(e) time.sleep(1) else: yield url break proc.terminate() proc.wait() del data @pytest.fixture(scope="session") def username(): """Default username for tests""" return "admin" @pytest.fixture(scope="session") def password(): """Default password for tests""" return "admin" @pytest.fixture def service_name(): """A randomly generated service name""" return "Test service " + random.randbytes(3).hex() # Test webserver @pytest.fixture def httpd(temp): """Test httpd server""" proc = [] def start(config): print(config) proc.append(subprocess.Popen(["httpd", "-DFOREGROUND", "-f", temp(config)])) time.sleep(1) # Sleep so apache can start yield start for pid in proc: pid.terminate() pid.wait() @pytest.fixture def httpd_minimal(): """Minimal httpd config""" rootDirectory = tempfile.TemporaryDirectory() with open(os.path.join(rootDirectory.name, "index.html"), "w") as handle: handle.write("Hello world!") yield f""" ServerName localhost ServerRoot /usr/lib/httpd PidFile /tmp/apache.pid LoadModule mpm_event_module modules/mod_mpm_event.so LoadModule unixd_module modules/mod_unixd.so LoadModule env_module modules/mod_env.so LoadModule dir_module modules/mod_dir.so LoadModule log_config_module modules/mod_log_config.so LoadModule authn_core_module modules/mod_authn_core.so LoadModule authz_core_module modules/mod_authz_core.so LoadModule authz_user_module modules/mod_authz_user.so LogLevel debug ErrorLog /dev/stderr TransferLog /dev/stdout Listen 127.0.0.1:8123 DocumentRoot {rootDirectory.name} DirectoryIndex index.html """ del rootDirectory @pytest.fixture def httpd_saml(httpd_minimal): return (httpd_minimal + """ LoadModule auth_mellon_module modules/mod_auth_mellon.so <Location /> Require valid-user AuthType "Mellon" MellonEnable "auth" MellonEndpointPath "/mellon" MellonSPPrivateKeyFile {key} MellonSPCertFile {cert} MellonIdPMetadataFile {metadata} SetEnv MELLON_DISABLE_SAMESITE 1 </Location> """) @pytest.fixture def httpd_oidc(httpd_minimal): return (httpd_minimal + """ LoadModule auth_openidc_module modules/mod_auth_openidc.so OIDCProviderMetadataURL {metadata} OIDCClientID {client_id} OIDCClientSecret {client_secret} OIDCRedirectURI http://localhost:8123/redirect_uri OIDCCryptoPassphrase changeme OIDCScope "openid email profile" <Location /> Require valid-user AuthType "openid-connect" </Location> """)