e2e: add tests for proxy provider and outposts
This commit is contained in:
parent
6187436518
commit
f1ccef7f6a
|
@ -0,0 +1,93 @@
|
|||
"""Proxy and Outpost e2e tests"""
|
||||
from time import sleep
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
from docker.client import DockerClient, from_env
|
||||
from docker.models.containers import Container
|
||||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver.common.keys import Keys
|
||||
|
||||
from e2e.utils import USER, SeleniumTestCase
|
||||
from passbook.core.models import Application
|
||||
from passbook.flows.models import Flow
|
||||
from passbook.outposts.models import Outpost, OutpostDeploymentType, OutpostType
|
||||
from passbook.providers.proxy.models import ProxyProvider
|
||||
|
||||
|
||||
class TestProviderProxy(SeleniumTestCase):
|
||||
"""Proxy and Outpost e2e tests"""
|
||||
|
||||
proxy_container: Container
|
||||
|
||||
def tearDown(self) -> None:
|
||||
super().tearDown()
|
||||
self.proxy_container.kill()
|
||||
|
||||
def get_container_specs(self) -> Optional[Dict[str, Any]]:
|
||||
return {
|
||||
"image": "traefik/whoami:latest",
|
||||
"detach": True,
|
||||
"network_mode": "host",
|
||||
"auto_remove": True,
|
||||
}
|
||||
|
||||
def start_proxy(self, outpost: Outpost) -> Container:
|
||||
"""Start proxy container based on outpost created"""
|
||||
client: DockerClient = from_env()
|
||||
container = client.containers.run(
|
||||
image="beryju/passbook-proxy:latest",
|
||||
detach=True,
|
||||
network_mode="host",
|
||||
auto_remove=True,
|
||||
environment={
|
||||
"PASSBOOK_HOST": self.live_server_url,
|
||||
"PASSBOOK_TOKEN": outpost.token.token_uuid.hex,
|
||||
},
|
||||
)
|
||||
return container
|
||||
|
||||
def test_proxy_simple(self):
|
||||
"""Test simple outpost setup with single provider"""
|
||||
proxy: ProxyProvider = ProxyProvider.objects.create(
|
||||
name="proxy_provider",
|
||||
authorization_flow=Flow.objects.get(
|
||||
slug="default-provider-authorization-implicit-consent"
|
||||
),
|
||||
internal_host="http://localhost:80",
|
||||
external_host="http://localhost:4180",
|
||||
)
|
||||
# Ensure OAuth2 Params are set
|
||||
proxy.set_oauth_defaults()
|
||||
proxy.save()
|
||||
# we need to create an application to actually access the proxy
|
||||
Application.objects.create(name="proxy", slug="proxy", provider=proxy)
|
||||
outpost: Outpost = Outpost.objects.create(
|
||||
name="proxy_outpost",
|
||||
type=OutpostType.PROXY,
|
||||
deployment_type=OutpostDeploymentType.CUSTOM,
|
||||
)
|
||||
outpost.providers.add(proxy)
|
||||
outpost.save()
|
||||
|
||||
self.proxy_container = self.start_proxy(outpost)
|
||||
|
||||
# Wait until outpost healthcheck succeeds
|
||||
healthcheck_retries = 0
|
||||
while healthcheck_retries < 50:
|
||||
if outpost.health:
|
||||
break
|
||||
healthcheck_retries += 1
|
||||
sleep(0.5)
|
||||
|
||||
self.driver.get("http://localhost:4180")
|
||||
|
||||
self.driver.find_element(By.ID, "id_uid_field").click()
|
||||
self.driver.find_element(By.ID, "id_uid_field").send_keys(USER().username)
|
||||
self.driver.find_element(By.ID, "id_uid_field").send_keys(Keys.ENTER)
|
||||
self.driver.find_element(By.ID, "id_password").send_keys(USER().username)
|
||||
self.driver.find_element(By.ID, "id_password").send_keys(Keys.ENTER)
|
||||
|
||||
sleep(1)
|
||||
|
||||
full_body_text = self.driver.find_element(By.CSS_SELECTOR, "pre").text
|
||||
self.assertIn("X-Forwarded-Preferred-Username: pbadmin", full_body_text)
|
|
@ -17,6 +17,7 @@ from docker.models.containers import Container
|
|||
from selenium import webdriver
|
||||
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
|
||||
from selenium.webdriver.remote.webdriver import WebDriver
|
||||
from selenium.webdriver.support import expected_conditions as ec
|
||||
from selenium.webdriver.support.ui import WebDriverWait
|
||||
from structlog import get_logger
|
||||
|
||||
|
@ -50,6 +51,8 @@ class SeleniumTestCase(StaticLiveServerTestCase):
|
|||
def _start_container(self, specs: Dict[str, Any]) -> Container:
|
||||
client: DockerClient = from_env()
|
||||
container = client.containers.run(**specs)
|
||||
if "healthcheck" not in specs:
|
||||
return container
|
||||
while True:
|
||||
container.reload()
|
||||
status = container.attrs.get("State", {}).get("Health", {}).get("Status")
|
||||
|
@ -88,7 +91,7 @@ class SeleniumTestCase(StaticLiveServerTestCase):
|
|||
def wait_for_url(self, desired_url):
|
||||
"""Wait until URL is `desired_url`."""
|
||||
self.wait.until(
|
||||
lambda driver: driver.current_url == desired_url,
|
||||
ec.url_to_be(desired_url),
|
||||
f"URL {self.driver.current_url} doesn't match expected URL {desired_url}",
|
||||
)
|
||||
|
||||
|
|
|
@ -15,11 +15,8 @@ class CodeMirrorWidget(forms.Textarea):
|
|||
self.mode = mode
|
||||
|
||||
def render(self, *args, **kwargs):
|
||||
if "attrs" not in kwargs:
|
||||
kwargs["attrs"] = {}
|
||||
attrs = kwargs["attrs"]
|
||||
if "class" not in attrs:
|
||||
attrs["class"] = ""
|
||||
attrs = kwargs.setdefault("attrs", {})
|
||||
attrs.setdefault("class", "")
|
||||
attrs["class"] += " codemirror"
|
||||
attrs["data-cm-mode"] = self.mode
|
||||
return super().render(*args, **kwargs)
|
||||
|
|
|
@ -4,7 +4,7 @@ from django.core.management.base import BaseCommand, no_translations
|
|||
from passbook.flows.transfer.importer import FlowImporter
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
class Command(BaseCommand): # pragma: no cover
|
||||
"""Apply flow from commandline"""
|
||||
|
||||
@no_translations
|
||||
|
|
|
@ -1,11 +1,5 @@
|
|||
"""passbook lib template utilities"""
|
||||
from django.template import Context, Template, loader
|
||||
|
||||
|
||||
def render_from_string(tmpl: str, ctx: Context) -> str:
|
||||
"""Render template from string to string"""
|
||||
template = Template(tmpl)
|
||||
return template.render(ctx)
|
||||
from django.template import Context, loader
|
||||
|
||||
|
||||
def render_to_string(template_path: str, ctx: Context) -> str:
|
||||
|
|
|
@ -25,6 +25,19 @@ from passbook.lib.config import CONFIG
|
|||
from passbook.lib.logging import add_process_id
|
||||
from passbook.lib.sentry import before_send
|
||||
|
||||
|
||||
def j_print(event: str, log_level: str = "info", **kwargs):
|
||||
"""Print event in the same format as structlog with JSON.
|
||||
Used before structlog is configured."""
|
||||
data = {
|
||||
"event": event,
|
||||
"level": log_level,
|
||||
"logger": __name__,
|
||||
}
|
||||
data.update(**kwargs)
|
||||
print(dumps(data))
|
||||
|
||||
|
||||
LOGGER = structlog.get_logger()
|
||||
|
||||
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
||||
|
@ -276,16 +289,7 @@ if CONFIG.y("postgresql.backup"):
|
|||
AWS_STORAGE_BUCKET_NAME = CONFIG.y("postgresql.backup.bucket")
|
||||
AWS_S3_ENDPOINT_URL = CONFIG.y("postgresql.backup.host")
|
||||
AWS_DEFAULT_ACL = None
|
||||
print(
|
||||
dumps(
|
||||
{
|
||||
"event": "Database backup is configured.",
|
||||
"level": "info",
|
||||
"logger": __name__,
|
||||
"host": CONFIG.y("postgresql.backup.host"),
|
||||
}
|
||||
)
|
||||
)
|
||||
j_print("Database backup is configured.", host=CONFIG.y("postgresql.backup.host"))
|
||||
# Add automatic task to backup
|
||||
CELERY_BEAT_SCHEDULE["db_backup"] = {
|
||||
"task": "passbook.lib.tasks.backup_database",
|
||||
|
@ -295,15 +299,6 @@ if CONFIG.y("postgresql.backup"):
|
|||
# Sentry integration
|
||||
_ERROR_REPORTING = CONFIG.y_bool("error_reporting.enabled", False)
|
||||
if not DEBUG and _ERROR_REPORTING:
|
||||
print(
|
||||
dumps(
|
||||
{
|
||||
"event": "Error reporting is enabled.",
|
||||
"level": "info",
|
||||
"logger": __name__,
|
||||
}
|
||||
)
|
||||
)
|
||||
sentry_init(
|
||||
dsn="https://33cdbcb23f8b436dbe0ee06847410b67@sentry.beryju.org/3",
|
||||
integrations=[
|
||||
|
@ -316,6 +311,10 @@ if not DEBUG and _ERROR_REPORTING:
|
|||
environment=CONFIG.y("error_reporting.environment", "customer"),
|
||||
send_default_pii=CONFIG.y_bool("error_reporting.send_pii", False),
|
||||
)
|
||||
j_print(
|
||||
"Error reporting is enabled.",
|
||||
env=CONFIG.y("error_reporting.environment", "customer"),
|
||||
)
|
||||
|
||||
|
||||
# Static files (CSS, JavaScript, Images)
|
||||
|
@ -434,3 +433,5 @@ if DEBUG:
|
|||
MIDDLEWARE.append("debug_toolbar.middleware.DebugToolbarMiddleware")
|
||||
|
||||
INSTALLED_APPS.append("passbook.core.apps.PassbookCoreConfig")
|
||||
|
||||
j_print("Booting passbook", version=__version__)
|
||||
|
|
Reference in New Issue