feat(platform): close AI expense value loop
Add tenant-safe value, telemetry, connector, commercial, and production-readiness foundations.
This commit is contained in:
@@ -6,12 +6,15 @@ from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
|
||||
from app.api.deps import CurrentUserContext
|
||||
from app.core.config import Settings, get_settings
|
||||
from app.core import secret_box
|
||||
from app.core.config import Settings, get_settings
|
||||
from app.db.base import Base
|
||||
from app.models.hermes_config import HermesTaskConfig
|
||||
from app.models.knowledge_security import KnowledgeOnlyOfficeSession
|
||||
from app.models.system_model_setting import SystemModelSetting
|
||||
from app.models.system_setting import SystemSetting
|
||||
from app.models.system_setting_secret import SystemSettingSecret
|
||||
from app.models.tenant import Tenant
|
||||
from app.schemas.settings import SettingsWrite
|
||||
from app.services.knowledge import KnowledgeService
|
||||
from app.services.settings import SettingsService
|
||||
@@ -25,9 +28,24 @@ def build_session_factory(db_file: Path):
|
||||
SystemSetting.__table__.create(bind=engine)
|
||||
SystemSettingSecret.__table__.create(bind=engine)
|
||||
SystemModelSetting.__table__.create(bind=engine)
|
||||
Tenant.__table__.create(bind=engine)
|
||||
KnowledgeOnlyOfficeSession.__table__.create(bind=engine)
|
||||
HermesTaskConfig.__table__.create(bind=engine)
|
||||
return sessionmaker(bind=engine, autoflush=False, autocommit=False)
|
||||
|
||||
|
||||
def seed_tenant(db) -> None:
|
||||
db.add(
|
||||
Tenant(
|
||||
tenant_id="tenant-a",
|
||||
tenant_code="TENANT-A",
|
||||
name="租户 A",
|
||||
status="active",
|
||||
)
|
||||
)
|
||||
db.commit()
|
||||
|
||||
|
||||
def test_onlyoffice_config_is_read_only_for_admin_users(tmp_path, monkeypatch) -> None:
|
||||
env_file = tmp_path / ".env"
|
||||
env_file.write_text(
|
||||
@@ -36,7 +54,7 @@ def test_onlyoffice_config_is_read_only_for_admin_users(tmp_path, monkeypatch) -
|
||||
"ONLYOFFICE_ENABLED=true",
|
||||
"ONLYOFFICE_PUBLIC_URL=http://10.10.10.122:8082",
|
||||
"ONLYOFFICE_BACKEND_URL=http://main:8000",
|
||||
"ONLYOFFICE_JWT_SECRET=change-me-onlyoffice",
|
||||
"ONLYOFFICE_JWT_SECRET=change-me-onlyoffice-secret-32bytes",
|
||||
]
|
||||
)
|
||||
+ "\n",
|
||||
@@ -47,32 +65,42 @@ def test_onlyoffice_config_is_read_only_for_admin_users(tmp_path, monkeypatch) -
|
||||
get_settings.cache_clear()
|
||||
|
||||
try:
|
||||
service = KnowledgeService(storage_root=tmp_path)
|
||||
service.ensure_library_ready()
|
||||
session_factory = build_session_factory(tmp_path / "onlyoffice.db")
|
||||
with session_factory() as db:
|
||||
seed_tenant(db)
|
||||
service = KnowledgeService(
|
||||
storage_root=tmp_path,
|
||||
db=db,
|
||||
tenant_id="tenant-a",
|
||||
)
|
||||
service.ensure_library_ready()
|
||||
|
||||
document_id = "readonly-docx"
|
||||
folder = "制度政策"
|
||||
stored_name = f"{document_id}__制度预览.docx"
|
||||
target_path = tmp_path / "knowledge" / folder / stored_name
|
||||
target_path.write_bytes(b"fake-docx-content")
|
||||
document_id = "readonly-docx"
|
||||
folder = "制度政策"
|
||||
stored_name = f"{document_id}__制度预览.docx"
|
||||
target_path = (
|
||||
tmp_path / "knowledge" / "tenants" / "tenant-a" / folder / stored_name
|
||||
)
|
||||
target_path.write_bytes(b"fake-docx-content")
|
||||
|
||||
current_user = CurrentUserContext(
|
||||
username="admin",
|
||||
name="管理员",
|
||||
role_codes=["manager"],
|
||||
is_admin=True,
|
||||
)
|
||||
current_user = CurrentUserContext(
|
||||
username="admin",
|
||||
name="管理员",
|
||||
role_codes=["manager"],
|
||||
is_admin=True,
|
||||
tenant_id="tenant-a",
|
||||
)
|
||||
|
||||
config = service.build_onlyoffice_config(document_id, current_user)
|
||||
permissions = config.config["document"]["permissions"]
|
||||
customization = config.config["editorConfig"]["customization"]
|
||||
config = service.build_onlyoffice_config(document_id, current_user)
|
||||
permissions = config.config["document"]["permissions"]
|
||||
customization = config.config["editorConfig"]["customization"]
|
||||
|
||||
assert config.documentServerUrl == "http://10.10.10.122:8082"
|
||||
assert config.config["editorConfig"]["mode"] == "view"
|
||||
assert permissions["edit"] is False
|
||||
assert permissions["download"] is True
|
||||
assert customization["autosave"] is False
|
||||
assert customization["forcesave"] is False
|
||||
assert config.documentServerUrl == "http://10.10.10.122:8082"
|
||||
assert config.config["editorConfig"]["mode"] == "view"
|
||||
assert permissions["edit"] is False
|
||||
assert permissions["download"] is True
|
||||
assert customization["autosave"] is False
|
||||
assert customization["forcesave"] is False
|
||||
finally:
|
||||
monkeypatch.setitem(Settings.model_config, "env_file", original_env_file)
|
||||
get_settings.cache_clear()
|
||||
@@ -102,35 +130,45 @@ def test_onlyoffice_config_prefers_saved_settings_snapshot(tmp_path, monkeypatch
|
||||
|
||||
try:
|
||||
with session_factory() as db:
|
||||
seed_tenant(db)
|
||||
service = SettingsService(db)
|
||||
payload = service.get_settings_snapshot().model_dump()
|
||||
payload["renderForm"]["enabled"] = True
|
||||
payload["renderForm"]["publicUrl"] = "http://10.10.10.122:8082"
|
||||
payload["renderForm"]["jwtSecret"] = "change-me-onlyoffice"
|
||||
payload["renderForm"]["jwtSecret"] = "change-me-onlyoffice-secret-32bytes"
|
||||
service.save_settings_snapshot(SettingsWrite(**payload))
|
||||
|
||||
service = KnowledgeService(storage_root=tmp_path)
|
||||
service.ensure_library_ready()
|
||||
with session_factory() as db:
|
||||
service = KnowledgeService(
|
||||
storage_root=tmp_path,
|
||||
db=db,
|
||||
tenant_id="tenant-a",
|
||||
)
|
||||
service.ensure_library_ready()
|
||||
|
||||
document_id = "db-backed-docx"
|
||||
folder = "制度政策"
|
||||
stored_name = f"{document_id}__制度预览.docx"
|
||||
target_path = tmp_path / "knowledge" / folder / stored_name
|
||||
target_path.write_bytes(b"fake-docx-content")
|
||||
document_id = "db-backed-docx"
|
||||
folder = "制度政策"
|
||||
stored_name = f"{document_id}__制度预览.docx"
|
||||
target_path = (
|
||||
tmp_path / "knowledge" / "tenants" / "tenant-a" / folder / stored_name
|
||||
)
|
||||
target_path.write_bytes(b"fake-docx-content")
|
||||
|
||||
current_user = CurrentUserContext(
|
||||
username="admin",
|
||||
name="管理员",
|
||||
role_codes=["manager"],
|
||||
is_admin=True,
|
||||
)
|
||||
current_user = CurrentUserContext(
|
||||
username="admin",
|
||||
name="管理员",
|
||||
role_codes=["manager"],
|
||||
is_admin=True,
|
||||
tenant_id="tenant-a",
|
||||
)
|
||||
|
||||
config = service.build_onlyoffice_config(document_id, current_user)
|
||||
config = service.build_onlyoffice_config(document_id, current_user)
|
||||
|
||||
assert config.documentServerUrl == "http://10.10.10.122:8082"
|
||||
assert config.config["document"]["url"].startswith(
|
||||
"http://main:8000/api/v1/knowledge/documents/db-backed-docx/onlyoffice/content?access_token="
|
||||
)
|
||||
assert config.documentServerUrl == "http://10.10.10.122:8082"
|
||||
assert config.config["document"]["url"].startswith(
|
||||
"http://main:8000/api/v1/knowledge/documents/"
|
||||
"db-backed-docx/onlyoffice/content?access_token="
|
||||
)
|
||||
finally:
|
||||
monkeypatch.setitem(Settings.model_config, "env_file", original_env_file)
|
||||
get_settings.cache_clear()
|
||||
|
||||
Reference in New Issue
Block a user