feat(PORT-004A): add workspace bootstrap and scoped settings
This commit is contained in:
@@ -16,12 +16,25 @@ def test_homepage_and_options_page_render() -> None:
|
||||
|
||||
page.goto(BASE_URL, wait_until="domcontentloaded", timeout=30000)
|
||||
expect(page).to_have_title("NiceGUI")
|
||||
expect(page.locator("text=Create a private workspace URL").first).to_be_visible(timeout=10000)
|
||||
page.get_by_role("link", name="Get started").click()
|
||||
page.wait_for_url(f"{BASE_URL}/*", timeout=15000)
|
||||
workspace_url = page.url
|
||||
workspace_id = workspace_url.removeprefix(f"{BASE_URL}/")
|
||||
assert workspace_id
|
||||
cookies = page.context.cookies()
|
||||
workspace_cookie = next(cookie for cookie in cookies if cookie["name"] == "workspace_id")
|
||||
assert workspace_cookie["value"] == workspace_id
|
||||
expect(page.locator("text=Vault Dashboard").first).to_be_visible(timeout=10000)
|
||||
expect(page.locator("text=Overview").first).to_be_visible(timeout=10000)
|
||||
expect(page.locator("text=Live quote source:").first).to_be_visible(timeout=15000)
|
||||
expect(page.locator("text=Alert Status").first).to_be_visible(timeout=15000)
|
||||
page.screenshot(path=str(ARTIFACTS / "overview.png"), full_page=True)
|
||||
|
||||
page.goto(BASE_URL, wait_until="domcontentloaded", timeout=30000)
|
||||
page.wait_for_url(workspace_url, timeout=15000)
|
||||
expect(page.locator("text=Alert Status").first).to_be_visible(timeout=15000)
|
||||
|
||||
page.goto(f"{BASE_URL}/backtests", wait_until="networkidle", timeout=30000)
|
||||
expect(page.locator("text=Backtests").first).to_be_visible(timeout=15000)
|
||||
expect(page.locator("text=Scenario Form").first).to_be_visible(timeout=15000)
|
||||
@@ -79,15 +92,35 @@ def test_homepage_and_options_page_render() -> None:
|
||||
assert "RuntimeError" not in body_text
|
||||
page.screenshot(path=str(ARTIFACTS / "options.png"), full_page=True)
|
||||
|
||||
page.goto(f"{BASE_URL}/settings", wait_until="domcontentloaded", timeout=30000)
|
||||
page.goto(f"{workspace_url}/settings", wait_until="domcontentloaded", timeout=30000)
|
||||
assert page.url.endswith("/settings")
|
||||
expect(page.locator("text=Settings").first).to_be_visible(timeout=15000)
|
||||
expect(page.locator("text=Collateral entry basis").first).to_be_visible(timeout=15000)
|
||||
expect(page.locator("text=Entry price ($/oz)").first).to_be_visible(timeout=15000)
|
||||
budget_input = page.get_by_label("Monthly hedge budget ($)")
|
||||
budget_input.fill("12345")
|
||||
page.get_by_role("button", name="Save settings").click()
|
||||
expect(page.locator("text=Settings saved successfully").first).to_be_visible(timeout=15000)
|
||||
page.reload(wait_until="domcontentloaded", timeout=30000)
|
||||
expect(page.get_by_label("Monthly hedge budget ($)")).to_have_value("12345")
|
||||
settings_text = page.locator("body").inner_text(timeout=15000)
|
||||
assert "RuntimeError" not in settings_text
|
||||
assert "Server error" not in settings_text
|
||||
page.screenshot(path=str(ARTIFACTS / "settings.png"), full_page=True)
|
||||
|
||||
second_context = browser.new_context(viewport={"width": 1440, "height": 1000})
|
||||
second_page = second_context.new_page()
|
||||
second_page.goto(BASE_URL, wait_until="domcontentloaded", timeout=30000)
|
||||
expect(second_page.locator("text=Create a private workspace URL").first).to_be_visible(timeout=10000)
|
||||
second_page.get_by_role("link", name="Get started").click()
|
||||
second_page.wait_for_url(f"{BASE_URL}/*", timeout=15000)
|
||||
second_workspace_url = second_page.url
|
||||
assert second_workspace_url != workspace_url
|
||||
second_page.goto(f"{second_workspace_url}/settings", wait_until="domcontentloaded", timeout=30000)
|
||||
expect(second_page.get_by_label("Monthly hedge budget ($)")).to_have_value("8000")
|
||||
second_page.close()
|
||||
second_context.close()
|
||||
|
||||
page.goto(f"{BASE_URL}/hedge", wait_until="domcontentloaded", timeout=30000)
|
||||
expect(page.locator("text=Hedge Analysis").first).to_be_visible(timeout=15000)
|
||||
expect(page.locator("text=Strategy selector").first).to_be_visible(timeout=15000)
|
||||
|
||||
100
tests/test_workspace.py
Normal file
100
tests/test_workspace.py
Normal file
@@ -0,0 +1,100 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from uuid import uuid4
|
||||
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from app.main import app
|
||||
from app.models.workspace import WorkspaceRepository
|
||||
|
||||
UUID4_RE = re.compile(
|
||||
r"^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$",
|
||||
re.IGNORECASE,
|
||||
)
|
||||
|
||||
|
||||
def _install_workspace_repo(tmp_path, monkeypatch) -> WorkspaceRepository:
|
||||
from app.models import workspace as workspace_module
|
||||
|
||||
repo = WorkspaceRepository(base_path=tmp_path / "workspaces")
|
||||
monkeypatch.setattr(workspace_module, "_workspace_repo", repo)
|
||||
return repo
|
||||
|
||||
|
||||
def test_workspace_repository_persists_workspace_specific_portfolio_config(tmp_path) -> None:
|
||||
repo = WorkspaceRepository(base_path=tmp_path / "workspaces")
|
||||
workspace_id = str(uuid4())
|
||||
|
||||
created = repo.create_workspace(workspace_id)
|
||||
created.loan_amount = 123_456.0
|
||||
repo.save_portfolio_config(workspace_id, created)
|
||||
|
||||
reloaded = repo.load_portfolio_config(workspace_id)
|
||||
|
||||
assert repo.workspace_exists(workspace_id)
|
||||
assert reloaded.loan_amount == 123_456.0
|
||||
assert reloaded.gold_value == created.gold_value
|
||||
|
||||
|
||||
def test_root_without_workspace_cookie_shows_welcome_page(tmp_path, monkeypatch) -> None:
|
||||
_install_workspace_repo(tmp_path, monkeypatch)
|
||||
|
||||
with TestClient(app) as client:
|
||||
response = client.get("/")
|
||||
|
||||
assert response.status_code == 200
|
||||
assert "Create a private workspace URL" in response.text
|
||||
assert "Get started" in response.text
|
||||
|
||||
|
||||
def test_bootstrap_endpoint_creates_workspace_cookie_and_redirects(tmp_path, monkeypatch) -> None:
|
||||
repo = _install_workspace_repo(tmp_path, monkeypatch)
|
||||
|
||||
with TestClient(app) as client:
|
||||
response = client.get("/workspaces/bootstrap", follow_redirects=False)
|
||||
|
||||
assert response.status_code in {302, 303, 307}
|
||||
location = response.headers["location"]
|
||||
workspace_id = location.strip("/")
|
||||
assert UUID4_RE.match(workspace_id)
|
||||
assert repo.workspace_exists(workspace_id)
|
||||
assert response.cookies.get("workspace_id") == workspace_id
|
||||
|
||||
|
||||
def test_root_with_valid_workspace_cookie_redirects_to_workspace(tmp_path, monkeypatch) -> None:
|
||||
repo = _install_workspace_repo(tmp_path, monkeypatch)
|
||||
workspace_id = str(uuid4())
|
||||
repo.create_workspace(workspace_id)
|
||||
|
||||
with TestClient(app) as client:
|
||||
response = client.get("/", cookies={"workspace_id": workspace_id}, follow_redirects=False)
|
||||
|
||||
assert response.status_code in {302, 303, 307}
|
||||
assert response.headers["location"] == f"/{workspace_id}"
|
||||
|
||||
|
||||
def test_unknown_workspace_route_shows_friendly_recovery_path(tmp_path, monkeypatch) -> None:
|
||||
_install_workspace_repo(tmp_path, monkeypatch)
|
||||
|
||||
with TestClient(app) as client:
|
||||
response = client.get("/00000000-0000-4000-8000-000000000000")
|
||||
|
||||
assert response.status_code == 200
|
||||
assert "Workspace not found" in response.text
|
||||
assert "Get started" in response.text
|
||||
|
||||
|
||||
def test_workspace_settings_round_trip_uses_workspace_storage(tmp_path, monkeypatch) -> None:
|
||||
repo = _install_workspace_repo(tmp_path, monkeypatch)
|
||||
workspace_id = str(uuid4())
|
||||
config = repo.create_workspace(workspace_id)
|
||||
config.monthly_budget = 9_999.0
|
||||
repo.save_portfolio_config(workspace_id, config)
|
||||
|
||||
with TestClient(app) as client:
|
||||
response = client.get(f"/{workspace_id}/settings")
|
||||
|
||||
assert response.status_code == 200
|
||||
assert "Settings" in response.text
|
||||
assert "9,999" in response.text or "9999" in response.text
|
||||
Reference in New Issue
Block a user