T04: DMS plugin backend — folders + files + preview + OnlyOffice + shares + search + bulk — 106 tests, 97.90% coverage

This commit is contained in:
leocrm-bot
2026-06-29 20:48:58 +02:00
parent a2452cc04b
commit fdb41dade1
14 changed files with 3764 additions and 66 deletions
+67 -1
View File
@@ -7,6 +7,8 @@ Auth helpers talk to the HTTP API (integration tests).
from __future__ import annotations
import asyncio
import os
import shutil
from collections.abc import AsyncGenerator
from typing import Any
@@ -24,6 +26,7 @@ from sqlalchemy.ext.asyncio import (
from app.core.auth import hash_password
from app.core.db import Base, close_engine, reset_engine_for_testing
from app.core.service_container import get_container # noqa: F401
from app.main import create_app
from app.models.ai_conversation import AIConversation, AIMessage # noqa: F401
from app.models.company import Company
@@ -33,9 +36,15 @@ from app.models.role import Role
from app.models.tenant import Tenant
from app.models.user import User, UserTenant
from app.models.workflow import Workflow, WorkflowInstance, WorkflowStepHistory # noqa: F401
from app.plugins.builtins.dms import DmsPlugin # noqa: F401
from app.plugins.builtins.dms.models import File as DmsFile # noqa: F401
from app.plugins.builtins.dms.models import Folder # noqa: F401
from app.plugins.builtins.entity_links.models import EntityLink # noqa: F401
from app.plugins.builtins.permissions import PermissionsPlugin # noqa: F401
from app.plugins.builtins.permissions.models import Permission, ShareLink # noqa: F401
from app.plugins.builtins.tags.models import Tag, TagAssignment # noqa: F401
from app.plugins.registry import reset_registry_for_testing # noqa: F401
from app.services.plugin_service import reset_plugin_service_for_testing # noqa: F401
# Import plugin models so Base.metadata.create_all includes their tables
@@ -94,7 +103,7 @@ def clean_tables(db_setup):
# TRUNCATE all tables with CASCADE — fast and reliable isolation
conn.execute(
text(
"TRUNCATE TABLE entity_links, share_links, permissions, tag_assignments, tags, workflow_step_history, workflow_instances, workflows, ai_messages, ai_conversations, plugin_migrations, plugins, company_contacts, contacts, api_tokens, password_reset_tokens, notifications, deletion_log, audit_log, sessions, roles, companies, user_tenants, users, tenants CASCADE;"
"TRUNCATE TABLE files, folders, entity_links, share_links, permissions, tag_assignments, tags, workflow_step_history, workflow_instances, workflows, ai_messages, ai_conversations, plugin_migrations, plugins, company_contacts, contacts, api_tokens, password_reset_tokens, notifications, deletion_log, audit_log, sessions, roles, companies, user_tenants, users, tenants CASCADE;"
)
)
conn.commit()
@@ -283,3 +292,60 @@ async def get_auth_client(
"""Return a client that's logged in."""
await login_client(client, email, password)
return client
DMS_TEST_STORAGE = "/tmp/dms_test"
@pytest_asyncio.fixture
async def dms_app(engine: AsyncEngine, redis_client):
"""FastAPI app with DMS + Permissions plugins registered."""
os.environ["DMS_STORAGE_BASE"] = DMS_TEST_STORAGE
reset_engine_for_testing(engine)
app = create_app()
registry = reset_registry_for_testing()
registry.initialize(engine, app)
container = get_container()
await container.initialize()
registry.register_plugin(PermissionsPlugin())
registry.register_plugin(DmsPlugin())
reset_plugin_service_for_testing(registry)
yield app
await close_engine()
# Cleanup test storage
if os.path.exists(DMS_TEST_STORAGE):
shutil.rmtree(DMS_TEST_STORAGE, ignore_errors=True)
@pytest_asyncio.fixture
async def dms_client(dms_app) -> AsyncClient:
transport = ASGITransport(app=dms_app)
async with AsyncClient(transport=transport, base_url="http://test") as c:
yield c
@pytest_asyncio.fixture
async def authed_client(
dms_client: AsyncClient, db_session: AsyncSession
) -> tuple[AsyncClient, dict]:
"""Authenticated admin client with seeded data and both plugins activated."""
seed = await seed_tenant_and_users(db_session)
await login_client(dms_client, "admin@tenanta.com")
# Install + activate permissions plugin first (DMS depends on it)
resp = await dms_client.post("/api/v1/plugins/permissions/install", headers=ORIGIN_HEADER)
assert resp.status_code == 200, f"Permissions install failed: {resp.text}"
resp = await dms_client.post("/api/v1/plugins/permissions/activate", headers=ORIGIN_HEADER)
assert resp.status_code == 200, f"Permissions activate failed: {resp.text}"
# Install + activate DMS plugin
resp = await dms_client.post("/api/v1/plugins/dms/install", headers=ORIGIN_HEADER)
assert resp.status_code == 200, f"DMS install failed: {resp.text}"
resp = await dms_client.post("/api/v1/plugins/dms/activate", headers=ORIGIN_HEADER)
assert resp.status_code == 200, f"DMS activate failed: {resp.text}"
return dms_client, seed