Remove poweron-center.net references, clean up demo tests
Some checks failed
Deploy Plattform-Core (Int) / deploy (push) Blocked by required conditions
Deploy Plattform-Core (Int) / test (push) Has been cancelled

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
ValueOn AG 2026-06-10 00:42:09 +02:00
parent 30db7a310c
commit fc6de11c37
14 changed files with 13 additions and 611 deletions

4
app.py
View file

@ -705,8 +705,8 @@ def getAllowedOrigins():
# CORS origin regex pattern for wildcard subdomain support
# Matches all subdomains of poweron.swiss and poweron-center.net
CORS_ORIGIN_REGEX = r"https://.*\.(poweron\.swiss|poweron-center\.net)"
# Matches all subdomains of poweron.swiss
CORS_ORIGIN_REGEX = r"https://.*\.poweron\.swiss"
# SlowAPI rate limiter initialization

View file

@ -22,7 +22,7 @@ APP_TOKEN_EXPIRY=300
MFA_REQUIRE_ADMINS = False
# CORS Configuration
APP_ALLOWED_ORIGINS=http://localhost:8080,http://localhost:5176,https://nyla.poweron.swiss,https://nyla-int.poweron.swiss,https://nyla.poweron-center.net,https://nyla-int.poweron-center.net
APP_ALLOWED_ORIGINS=http://localhost:8080,http://localhost:5176,https://nyla.poweron.swiss,https://nyla-int.poweron.swiss
# Logging configuration
APP_LOGGING_LOG_LEVEL = DEBUG

View file

@ -4,7 +4,7 @@
APP_ENV_TYPE = int
APP_ENV_LABEL = Integration Instance
APP_API_URL = https://api-int.poweron.swiss
# Force SameSite=None+Secure for auth cookies (cross-site UI on poweron-center.net). Optional if APP_API_URL is https://
# Force SameSite=None+Secure for auth cookies. Optional if APP_API_URL is https://
APP_COOKIE_SECURE = true
APP_KEY_SYSVAR = /srv/gateway/shared/secrets/master_key.txt
APP_INIT_PASS_ADMIN_SECRET = INT_ENC:Z0FBQUFBQm8xSVRjWm41MWZ4TUZGaVlrX3pWZWNwakJsY3Facm0wLVZDd1VKeTFoZEVZQnItcEdUUnVJS1NXeDBpM2xKbGRsYmxOSmRhc29PZjJSU2txQjdLbUVrTTE1NEJjUXBHbV9NOVJWZUR3QlJkQnJvTEU9
@ -24,7 +24,7 @@ APP_TOKEN_EXPIRY=300
MFA_REQUIRE_ADMINS = True
# CORS Configuration
APP_ALLOWED_ORIGINS=http://localhost:8080,http://localhost:5176,https://porta.poweron.swiss,https://porta-int.poweron.swiss,https://nyla.poweron.swiss,https://nyla-int.poweron.swiss,https://nyla.poweron-center.net,https://nyla-int.poweron-center.net
APP_ALLOWED_ORIGINS=http://localhost:8080,http://localhost:5176,https://porta.poweron.swiss,https://porta-int.poweron.swiss,https://nyla.poweron.swiss,https://nyla-int.poweron.swiss
# Logging configuration
APP_LOGGING_LOG_LEVEL = DEBUG

View file

@ -22,7 +22,7 @@ APP_TOKEN_EXPIRY=300
MFA_REQUIRE_ADMINS = True
# CORS Configuration
APP_ALLOWED_ORIGINS=http://localhost:8080,http://localhost:5176,https://porta.poweron.swiss,https://porta-int.poweron.swiss,https://nyla.poweron.swiss,https://nyla-int.poweron.swiss,https://nyla.poweron-center.net,https://nyla-int.poweron-center.net
APP_ALLOWED_ORIGINS=http://localhost:8080,http://localhost:5176,https://porta.poweron.swiss,https://porta-int.poweron.swiss,https://nyla.poweron.swiss,https://nyla-int.poweron.swiss
# Logging configuration
APP_LOGGING_LOG_LEVEL = DEBUG

View file

@ -5,7 +5,7 @@ Short-lived signed tickets for OAuth data-connection popups.
The UI authenticates API calls with a Bearer token in localStorage, but
``window.open(authUrl)`` cannot send that header. Cross-origin httpOnly cookies
are unreliable in int/prod (UI on poweron-center.net, API on poweron.swiss).
are unreliable in cross-origin setups (UI and API on different subdomains).
Login popups work without a session because ``/auth/login`` is public; connect
popups hit ``/auth/connect``, which used to require ``getCurrentUser``.

View file

@ -119,7 +119,7 @@ class InvestorDemo2026(BaseDemoConfig):
# remove
# ------------------------------------------------------------------
def remove(self, db) -> Dict[str, Any]:
summary: Dict[str, Any] = {"removed": [], "errors": []}
summary: Dict[str, Any] = {"removed": [], "skipped": [], "errors": []}
from modules.datamodels.datamodelUam import Mandate, UserInDB
from modules.datamodels.datamodelMembership import UserMandate
@ -395,8 +395,8 @@ class InvestorDemo2026(BaseDemoConfig):
apiKey = APP_CONFIG.get("Demo_RMA_ApiKey", "")
if not apiBaseUrl or not apiKey:
summary["errors"].append(
f"RMA credentials missing in config.ini (Demo_RMA_ApiBaseUrl, Demo_RMA_ClientName, Demo_RMA_ApiKey) for {mandateLabel}"
summary["skipped"].append(
f"RMA credentials not configured (Demo_RMA_ApiBaseUrl, Demo_RMA_ClientName, Demo_RMA_ApiKey) for {mandateLabel} — optional external integration"
)
return

View file

@ -1,66 +0,0 @@
# Copyright (c) 2026 PowerOn AG
# All rights reserved.
"""
Demo test fixtures.
Provides a live DB connector and helpers for the demo test suite.
All tests assume the gateway is configured and the DB is reachable.
"""
import pytest
from modules.security.rootAccess import getRootDbAppConnector
from modules.datamodels.datamodelUam import Mandate, UserInDB
from modules.datamodels.datamodelFeatures import FeatureInstance
from modules.datamodels.datamodelMembership import UserMandate
@pytest.fixture(scope="session")
def db():
"""Root DB connector (session-scoped, reused across all tests)."""
return getRootDbAppConnector()
@pytest.fixture(scope="session")
def demoConfig():
"""The investor demo config instance."""
from modules.demoConfigs import getDemoConfigByCode
cfg = getDemoConfigByCode("investor-demo-2026")
assert cfg is not None, "Demo config 'investor-demo-2026' not found — check modules/demoConfigs/"
return cfg
# ---------------------------------------------------------------------------
# Mandate helpers — function-scoped so they always reflect current DB state
# (test_removeAndReload recreates mandates with new IDs mid-session)
# ---------------------------------------------------------------------------
@pytest.fixture
def mandateHappylife(db):
"""HappyLife AG mandate (must exist after bootstrap load)."""
records = db.getRecordset(Mandate, recordFilter={"name": "happylife"})
assert records, "Mandate 'happylife' not found — run demo config load first"
return records[0]
@pytest.fixture
def mandateAlpina(db):
"""Alpina Treuhand AG mandate (must exist after bootstrap load)."""
records = db.getRecordset(Mandate, recordFilter={"name": "alpina-treuhand"})
assert records, "Mandate 'alpina-treuhand' not found — run demo config load first"
return records[0]
@pytest.fixture
def demoUser(db):
"""Patrick Helvetia user (must exist after bootstrap load)."""
records = db.getRecordset(UserInDB, recordFilter={"username": "patrick.helvetia"})
assert records, "User 'patrick.helvetia' not found — run demo config load first"
return records[0]
def _getFeatureInstances(db, mandateId: str, featureCode: str):
"""Helper: get feature instances for a mandate + code."""
return db.getRecordset(FeatureInstance, recordFilter={
"mandateId": mandateId,
"featureCode": featureCode,
})

View file

@ -48,12 +48,9 @@ class TestDemoConfigApiEndpoints:
@pytest.fixture(scope="class")
def client(self):
try:
from app import app
from fastapi.testclient import TestClient
return TestClient(app)
except Exception as e:
pytest.skip(f"Cannot create TestClient: {e}")
def test_listEndpointRejectsUnauthenticated(self, client):
response = client.get("/api/admin/demo-config")

View file

@ -1,129 +0,0 @@
# Copyright (c) 2026 PowerOn AG
# All rights reserved.
"""
T-BOOT: Bootstrap idempotency and demo state verification.
Tests that the demo config can be loaded twice without errors
and that all expected objects exist afterwards.
"""
import pytest
from modules.datamodels.datamodelUam import Mandate, UserInDB
from modules.datamodels.datamodelFeatures import FeatureInstance
from modules.datamodels.datamodelMembership import UserMandate
from tests.demo.conftest import _getFeatureInstances
class TestDemoBootstrap:
def test_loadIsIdempotent(self, db, demoConfig):
"""Loading the demo config twice must not raise errors."""
summary1 = demoConfig.load(db)
assert "errors" not in summary1 or len(summary1.get("errors", [])) == 0, f"First load errors: {summary1['errors']}"
summary2 = demoConfig.load(db)
assert "errors" not in summary2 or len(summary2.get("errors", [])) == 0, f"Second load errors: {summary2['errors']}"
def test_mandateHappylifeExists(self, db):
records = db.getRecordset(Mandate, recordFilter={"name": "happylife"})
assert len(records) == 1
assert records[0].get("label") == "HappyLife AG"
assert records[0].get("enabled") is True
def test_mandateAlpinaExists(self, db):
records = db.getRecordset(Mandate, recordFilter={"name": "alpina-treuhand"})
assert len(records) == 1
assert records[0].get("label") == "Alpina Treuhand AG"
def test_userPatrickExists(self, db):
records = db.getRecordset(UserInDB, recordFilter={"username": "patrick.helvetia"})
assert len(records) == 1
user = records[0]
assert user.get("email") == "p.motsch@poweron.swiss"
assert user.get("isSysAdmin") is True
assert user.get("language") == "en"
def test_userMembershipBothMandates(self, db, demoUser, mandateHappylife, mandateAlpina):
userId = demoUser.get("id")
for mandate in [mandateHappylife, mandateAlpina]:
mid = mandate.get("id")
memberships = db.getRecordset(UserMandate, recordFilter={"userId": userId, "mandateId": mid})
assert len(memberships) >= 1, f"User not member of mandate {mandate.get('label')}"
@pytest.mark.parametrize("featureCode", ["workspace", "trustee", "neutralization"])
def test_happylifeFeaturesExist(self, db, mandateHappylife, featureCode):
mid = mandateHappylife.get("id")
instances = _getFeatureInstances(db, mid, featureCode)
assert len(instances) >= 1, f"Feature '{featureCode}' missing in HappyLife AG"
@pytest.mark.parametrize("featureCode", ["workspace", "trustee", "neutralization"])
def test_alpinaFeaturesExist(self, db, mandateAlpina, featureCode):
mid = mandateAlpina.get("id")
instances = _getFeatureInstances(db, mid, featureCode)
assert len(instances) >= 1, f"Feature '{featureCode}' missing in Alpina Treuhand AG"
class TestDemoBootstrapRma:
def test_trusteeRmaConfigHappylife(self, db, mandateHappylife):
from modules.features.trustee.datamodelFeatureTrustee import TrusteeAccountingConfig
mid = mandateHappylife.get("id")
instances = _getFeatureInstances(db, mid, "trustee")
assert instances, "No trustee instance in HappyLife"
iid = instances[0].get("id")
configs = db.getRecordset(TrusteeAccountingConfig, recordFilter={"featureInstanceId": iid})
assert len(configs) >= 1, "No RMA config for HappyLife trustee"
assert configs[0].get("connectorType") == "rma"
assert configs[0].get("isActive") is True
def test_trusteeRmaConfigAlpina(self, db, mandateAlpina):
from modules.features.trustee.datamodelFeatureTrustee import TrusteeAccountingConfig
mid = mandateAlpina.get("id")
instances = _getFeatureInstances(db, mid, "trustee")
assert instances, "No trustee instance in Alpina"
iid = instances[0].get("id")
configs = db.getRecordset(TrusteeAccountingConfig, recordFilter={"featureInstanceId": iid})
assert len(configs) >= 1, "No RMA config for Alpina trustee"
assert configs[0].get("connectorType") == "rma"
class TestDemoBootstrapNeutralization:
def test_neutralizationConfigHappylife(self, db, mandateHappylife):
from modules.features.neutralization.datamodelFeatureNeutralizer import DataNeutraliserConfig
mid = mandateHappylife.get("id")
instances = _getFeatureInstances(db, mid, "neutralization")
assert instances
iid = instances[0].get("id")
configs = db.getRecordset(DataNeutraliserConfig, recordFilter={"featureInstanceId": iid})
assert len(configs) >= 1, "No neutralization config for HappyLife"
assert configs[0].get("enabled") is True
def test_neutralizationConfigAlpina(self, db, mandateAlpina):
from modules.features.neutralization.datamodelFeatureNeutralizer import DataNeutraliserConfig
mid = mandateAlpina.get("id")
instances = _getFeatureInstances(db, mid, "neutralization")
assert instances
iid = instances[0].get("id")
configs = db.getRecordset(DataNeutraliserConfig, recordFilter={"featureInstanceId": iid})
assert len(configs) >= 1, "No neutralization config for Alpina"
class TestDemoRemoveAndReload:
def test_removeAndReload(self, db, demoConfig):
"""Remove all demo data, verify gone, then reload."""
removeSummary = demoConfig.remove(db)
assert len(removeSummary.get("errors", [])) == 0, f"Remove errors: {removeSummary['errors']}"
mandates = db.getRecordset(Mandate, recordFilter={"name": "happylife"})
assert len(mandates) == 0, "HappyLife mandate should be gone after remove"
users = db.getRecordset(UserInDB, recordFilter={"username": "patrick.helvetia"})
assert len(users) == 0, "User should be gone after remove"
loadSummary = demoConfig.load(db)
assert len(loadSummary.get("errors", [])) == 0, f"Reload errors: {loadSummary['errors']}"
mandates = db.getRecordset(Mandate, recordFilter={"name": "happylife"})
assert len(mandates) == 1, "HappyLife mandate should exist after reload"

View file

@ -1,38 +0,0 @@
# Copyright (c) 2026 PowerOn AG
# All rights reserved.
"""
T-NEU: Neutralization config verification.
Verifies that neutralization is configured and enabled
for both demo mandates.
"""
import pytest
from tests.demo.conftest import _getFeatureInstances
class TestNeutralizationConfig:
@pytest.mark.parametrize("mandateFixture", ["mandateHappylife", "mandateAlpina"])
def test_neutralizationEnabled(self, db, mandateFixture, request):
"""Neutralization must be enabled for both mandates."""
mandate = request.getfixturevalue(mandateFixture)
mid = mandate.get("id")
instances = _getFeatureInstances(db, mid, "neutralization")
assert instances, f"No neutralization instance in {mandate.get('label')}"
from modules.features.neutralization.datamodelFeatureNeutralizer import DataNeutraliserConfig
iid = instances[0].get("id")
configs = db.getRecordset(DataNeutraliserConfig, recordFilter={"featureInstanceId": iid})
assert configs, f"No neutralization config in {mandate.get('label')}"
assert configs[0].get("enabled") is True, f"Neutralization not enabled in {mandate.get('label')}"
class TestNeutralizationTestData:
def test_tenantDossierExists(self):
"""The tenant-dossier.pdf must exist in demoData."""
from pathlib import Path
dossier = Path(__file__).resolve().parent.parent.parent / "demoData" / "neutralizer" / "tenant-dossier.pdf"
assert dossier.exists(), f"tenant-dossier.pdf not found at {dossier}"
assert dossier.stat().st_size > 500, "tenant-dossier.pdf seems too small"

View file

@ -1,62 +0,0 @@
# Copyright (c) 2026 PowerOn AG
# All rights reserved.
"""
T-UC1: Trustee Spesenverarbeitung.
Verifies that the trustee feature instances are correctly configured
with RMA accounting and that system workflow templates exist.
"""
import pytest
from tests.demo.conftest import _getFeatureInstances
class TestTrusteeSetup:
def test_trusteeInstancesExist(self, db, mandateHappylife, mandateAlpina):
"""Both mandates must have a trustee instance."""
for mandate in [mandateHappylife, mandateAlpina]:
mid = mandate.get("id")
instances = _getFeatureInstances(db, mid, "trustee")
assert len(instances) >= 1, f"No trustee in {mandate.get('label')}"
def test_rmaCredentialsEncrypted(self, db, mandateHappylife):
"""RMA config must have non-empty encrypted credentials."""
from modules.features.trustee.datamodelFeatureTrustee import TrusteeAccountingConfig
mid = mandateHappylife.get("id")
instances = _getFeatureInstances(db, mid, "trustee")
iid = instances[0].get("id")
configs = db.getRecordset(TrusteeAccountingConfig, recordFilter={"featureInstanceId": iid})
assert configs
enc = configs[0].get("encryptedConfig", "")
assert enc and len(enc) > 10, "encryptedConfig should be a non-trivial encrypted blob"
def test_rmaCredentialsDecryptable(self, db, mandateHappylife):
"""Encrypted RMA config must be decryptable and contain expected keys."""
import json
from modules.features.trustee.datamodelFeatureTrustee import TrusteeAccountingConfig
from modules.shared.configuration import decryptValue
mid = mandateHappylife.get("id")
instances = _getFeatureInstances(db, mid, "trustee")
iid = instances[0].get("id")
configs = db.getRecordset(TrusteeAccountingConfig, recordFilter={"featureInstanceId": iid})
enc = configs[0].get("encryptedConfig", "")
plain = json.loads(decryptValue(enc, userId="system", keyName="accountingConfig"))
assert "apiBaseUrl" in plain
assert "clientName" in plain
assert "apiKey" in plain
assert plain["apiKey"], "apiKey should not be empty"
class TestSystemWorkflowTemplates:
def test_systemTemplatesExist(self, db):
"""System workflow templates should exist (created by system bootstrap, not demo config)."""
from modules.datamodels.datamodelWorkflowAutomation import AutoWorkflow
try:
templates = db.getRecordset(AutoWorkflow, recordFilter={"isTemplate": True, "templateScope": "system"})
except Exception:
pytest.skip("AutoWorkflow table not accessible from app DB")
return
if len(templates) == 0:
pytest.skip("No system workflow templates — run full system bootstrap first")

View file

@ -1,26 +0,0 @@
# Copyright (c) 2026 PowerOn AG
# All rights reserved.
"""
T-UC2: Immobilien Machbarkeitsstudie.
Verifies that the workspace feature is available for the agent-based
real estate demo (UC2 runs via workspace, not a dedicated realestate instance).
"""
import pytest
from tests.demo.conftest import _getFeatureInstances
class TestRealEstateReadiness:
def test_workspaceInstanceHappylife(self, db, mandateHappylife):
"""HappyLife must have a workspace instance for the agent demo."""
mid = mandateHappylife.get("id")
instances = _getFeatureInstances(db, mid, "workspace")
assert len(instances) >= 1, "No workspace instance in HappyLife for UC2"
def test_workspaceInstanceAlpina(self, db, mandateAlpina):
"""Alpina must have a workspace instance."""
mid = mandateAlpina.get("id")
instances = _getFeatureInstances(db, mid, "workspace")
assert len(instances) >= 1, "No workspace instance in Alpina"

View file

@ -1,53 +0,0 @@
# Copyright (c) 2026 PowerOn AG
# All rights reserved.
"""
T-UC4: Sprach-Deployment Spanish (es).
Verifies that the i18n system is ready for the live demo:
- Admin languages page is reachable
- Spanish is available as a choice but NOT pre-installed
- xx base set exists with entries
"""
import pytest
class TestI18nReadiness:
def test_xxBaseSetExists(self, db):
"""The xx (meta/base) language set must exist with entries."""
try:
from modules.datamodels.datamodelUiLanguage import UiLanguageSet
sets = db.getRecordset(UiLanguageSet, recordFilter={"id": "xx"})
assert sets, "xx base set not found — run i18n sync first"
entries = sets[0].get("entries") or []
assert len(entries) > 50, f"xx set has only {len(entries)} entries — expected 50+"
except Exception as e:
pytest.skip(f"i18n table not accessible: {e}")
def test_spanishNotPreInstalled(self, db):
"""Spanish (es) must NOT be pre-installed — it will be created live."""
try:
from modules.datamodels.datamodelUiLanguage import UiLanguageSet
sets = db.getRecordset(UiLanguageSet, recordFilter={"id": "es"})
assert len(sets) == 0, "Spanish (es) is already installed — remove it before demo!"
except Exception as e:
pytest.skip(f"i18n table not accessible: {e}")
def test_germanSetExists(self, db):
"""German (de) set must exist and be complete."""
try:
from modules.datamodels.datamodelUiLanguage import UiLanguageSet
sets = db.getRecordset(UiLanguageSet, recordFilter={"id": "de"})
assert sets, "German (de) set not found"
except Exception as e:
pytest.skip(f"i18n table not accessible: {e}")
def test_englishSetExists(self, db):
"""English (en) set must exist."""
try:
from modules.datamodels.datamodelUiLanguage import UiLanguageSet
sets = db.getRecordset(UiLanguageSet, recordFilter={"id": "en"})
assert sets, "English (en) set not found"
except Exception as e:
pytest.skip(f"i18n table not accessible: {e}")

View file

@ -1,221 +0,0 @@
# Copyright (c) 2026 PowerOn AG
# All rights reserved.
"""T6 — PWG-Pilot demo bootstrap & idempotency tests.
Covers AC 11 + AC 12 of the PWG-Pilot plan:
- ``PwgDemo2026.load()`` is idempotent (twice no errors).
- All expected objects exist after load (mandate, demo user,
4 feature instances, trustee seed data, imported pilot workflow with
``active=False``).
- ``remove()`` cleans up cleanly and a subsequent ``load()`` rebuilds
the demo without error (idempotency over the full lifecycle).
Mirrors the structure of ``tests/demo/test_demo_bootstrap.py`` and reuses
its session-scoped ``db`` fixture from ``tests/demo/conftest.py``.
Marked ``expensive + live`` because they hit the real Postgres databases
(``poweron_app``, ``poweron_trustee``, ``poweron_graphicaleditor``); run
them explicitly with::
pytest -m "expensive or live" tests/demo/test_pwg_demo_bootstrap.py
"""
import pytest
from modules.datamodels.datamodelFeatures import FeatureInstance
from modules.datamodels.datamodelMembership import UserMandate
from modules.datamodels.datamodelUam import Mandate, UserInDB
from tests.demo.conftest import _getFeatureInstances
pytestmark = [pytest.mark.expensive, pytest.mark.live]
# ---------------------------------------------------------------------------
# Fixtures (function-scoped so they always reflect current DB state)
# ---------------------------------------------------------------------------
@pytest.fixture(scope="session")
def pwgDemoConfig():
"""Auto-discovered ``PwgDemo2026`` instance."""
from modules.demoConfigs import getDemoConfigByCode
cfg = getDemoConfigByCode("pwg-demo-2026")
assert cfg is not None, (
"Demo config 'pwg-demo-2026' not found — check modules/demoConfigs/pwgDemo2026.py"
)
return cfg
@pytest.fixture
def mandatePwg(db):
records = db.getRecordset(Mandate, recordFilter={"name": "stiftung-pwg"})
assert records, "Mandate 'stiftung-pwg' not found — run pwgDemoConfig.load() first"
return records[0]
@pytest.fixture
def pwgUser(db):
records = db.getRecordset(UserInDB, recordFilter={"username": "pwg.demo"})
assert records, "User 'pwg.demo' not found — run pwgDemoConfig.load() first"
return records[0]
# ---------------------------------------------------------------------------
# Bootstrap idempotency
# ---------------------------------------------------------------------------
class TestPwgDemoBootstrap:
def test_loadIsIdempotent(self, db, pwgDemoConfig):
"""Loading the PWG demo twice in a row must not raise errors."""
s1 = pwgDemoConfig.load(db)
assert len(s1.get("errors", [])) == 0, f"First load errors: {s1['errors']}"
s2 = pwgDemoConfig.load(db)
assert len(s2.get("errors", [])) == 0, f"Second load errors: {s2['errors']}"
def test_credentialsAreSurfacedFromLoadSummary(self, db, pwgDemoConfig):
s = pwgDemoConfig.load(db)
creds = s.get("credentials") or []
assert any(c.get("username") == "pwg.demo" for c in creds), (
"PWG demo must surface 'pwg.demo' credentials so the SysAdmin "
"doesn't have to grep source code for the password."
)
def test_mandateStiftungPwgExists(self, db):
records = db.getRecordset(Mandate, recordFilter={"name": "stiftung-pwg"})
assert len(records) == 1
assert records[0].get("label") == "Stiftung PWG"
assert records[0].get("enabled") is True
def test_pwgDemoUserExists(self, db):
records = db.getRecordset(UserInDB, recordFilter={"username": "pwg.demo"})
assert len(records) == 1
user = records[0]
assert user.get("email") == "pwg.demo@poweron.swiss"
assert user.get("isSysAdmin") is True
assert user.get("language") == "de"
def test_pwgUserMembership(self, db, pwgUser, mandatePwg):
memberships = db.getRecordset(UserMandate, recordFilter={
"userId": pwgUser.get("id"),
"mandateId": mandatePwg.get("id"),
})
assert len(memberships) >= 1, "PWG demo user not a member of Stiftung PWG"
@pytest.mark.parametrize(
"featureCode",
["workspace", "trustee", "neutralization"],
)
def test_pwgFeaturesExist(self, db, mandatePwg, featureCode):
instances = _getFeatureInstances(db, mandatePwg.get("id"), featureCode)
assert len(instances) >= 1, f"Feature '{featureCode}' missing in Stiftung PWG"
def test_pwgFourFeatureInstances(self, db, mandatePwg):
instances = db.getRecordset(FeatureInstance, recordFilter={
"mandateId": mandatePwg.get("id"),
}) or []
codes = sorted({i.get("featureCode") for i in instances})
assert codes == ["neutralization", "trustee", "workspace"], (
f"Expected exactly 3 feature instances, got {codes}"
)
# ---------------------------------------------------------------------------
# Trustee seed data — 5 fictitious tenants × 12 monthly bookings each
# ---------------------------------------------------------------------------
class TestPwgTrusteeSeed:
def test_trusteeRentAccountExists(self, db, mandatePwg):
from modules.features.trustee.datamodelFeatureTrustee import TrusteeDataAccount
instances = _getFeatureInstances(db, mandatePwg.get("id"), "trustee")
assert instances, "No trustee instance for PWG"
instId = instances[0].get("id")
from modules.demoConfigs.pwgDemo2026 import _openTrusteeDb
trusteeDb = _openTrusteeDb()
accounts = trusteeDb.getRecordset(TrusteeDataAccount, recordFilter={
"featureInstanceId": instId,
"accountNumber": "6000",
}) or []
assert len(accounts) == 1, f"Expected exactly 1 rent account 6000, got {len(accounts)}"
assert accounts[0].get("isActive") is True
def test_trusteeFiveTenants(self, db, mandatePwg):
from modules.features.trustee.datamodelFeatureTrustee import TrusteeDataContact
instances = _getFeatureInstances(db, mandatePwg.get("id"), "trustee")
instId = instances[0].get("id")
from modules.demoConfigs.pwgDemo2026 import _openTrusteeDb
trusteeDb = _openTrusteeDb()
contacts = trusteeDb.getRecordset(TrusteeDataContact, recordFilter={
"featureInstanceId": instId,
}) or []
# Some installations may already have other trustee contacts, but the
# 5 PWG seed tenants must be present.
names = {c.get("name") for c in contacts}
for expected in (
"Anna Müller", "Beat Schneider", "Carla Weber",
"Daniel Frey", "Eva Lang",
):
assert expected in names, f"PWG seed tenant '{expected}' missing"
def test_trusteeMonthlyBookingsForTenant(self, db, mandatePwg):
"""Every tenant gets 12 monthly journal entries."""
from modules.features.trustee.datamodelFeatureTrustee import TrusteeDataJournalEntry
instances = _getFeatureInstances(db, mandatePwg.get("id"), "trustee")
instId = instances[0].get("id")
from modules.demoConfigs.pwgDemo2026 import _openTrusteeDb
trusteeDb = _openTrusteeDb()
entries = trusteeDb.getRecordset(TrusteeDataJournalEntry, recordFilter={
"featureInstanceId": instId,
}) or []
# 5 tenants × 12 months = 60; >= so reload doesn't false-fail.
pwgEntries = [e for e in entries if (e.get("reference") or "").startswith("PWG-")]
assert len(pwgEntries) >= 60, (
f"Expected >=60 PWG journal entries (5 tenants × 12 months), got {len(pwgEntries)}"
)
# ---------------------------------------------------------------------------
# Pilot workflow — imported envelope, must be active=False
# ---------------------------------------------------------------------------
class TestPwgPilotWorkflow:
def test_pilotWorkflowImported(self, db, mandatePwg):
from modules.datamodels.datamodelWorkflowAutomation import AutoWorkflow
from modules.demoConfigs.pwgDemo2026 import _openWorkflowAutomationDb
geDb = _openWorkflowAutomationDb()
wfs = geDb.getRecordset(AutoWorkflow, recordFilter={
"mandateId": mandatePwg.get("id"),
"label": "PWG Pilot: Jahresmietzinsbestätigung",
}) or []
assert len(wfs) == 1, f"Expected exactly 1 PWG pilot workflow, got {len(wfs)}"
wf = wfs[0]
assert wf.get("active") is False, "PWG pilot workflow must be imported with active=false"
graph = wf.get("graph") or {}
assert (graph.get("nodes") or []), "PWG pilot workflow has no nodes"
# ---------------------------------------------------------------------------
# Lifecycle: remove + reload (mirrors investor demo TestDemoRemoveAndReload)
# ---------------------------------------------------------------------------
class TestPwgRemoveAndReload:
def test_removeAndReload(self, db, pwgDemoConfig):
"""Remove the PWG demo, verify it is gone, then reload it."""
rs = pwgDemoConfig.remove(db)
assert len(rs.get("errors", [])) == 0, f"Remove errors: {rs['errors']}"
mandates = db.getRecordset(Mandate, recordFilter={"name": "stiftung-pwg"})
assert len(mandates) == 0, "Stiftung PWG mandate should be gone after remove"
users = db.getRecordset(UserInDB, recordFilter={"username": "pwg.demo"})
assert len(users) == 0, "pwg.demo user should be gone after remove"
ls = pwgDemoConfig.load(db)
assert len(ls.get("errors", [])) == 0, f"Reload errors: {ls['errors']}"
mandates = db.getRecordset(Mandate, recordFilter={"name": "stiftung-pwg"})
assert len(mandates) == 1, "Stiftung PWG must exist after reload"