| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664 |
- """B1测试公共配置。"""
- from __future__ import annotations
- import sys
- import os
- import tempfile
- from pathlib import Path
- import pytest
- BACKEND_ROOT = Path(__file__).resolve().parents[2]
- if str(BACKEND_ROOT) not in sys.path:
- sys.path.insert(0, str(BACKEND_ROOT))
- @pytest.fixture(scope="session")
- def flask_app():
- from app import app
- app.config.update(TESTING=True)
- return app
- @pytest.fixture()
- def client(flask_app):
- return flask_app.test_client()
- @pytest.fixture()
- def b2_app():
- database_url = os.environ.get("DMS_TEST_DATABASE_URL")
- jwt_secret = os.environ.get("DMS_TEST_JWT_SECRET")
- password = os.environ.get("DMS_TEST_USER_PASSWORD")
- if not database_url or not database_url.rsplit("/", 1)[-1].split("?", 1)[0].endswith("_test"):
- pytest.skip("DMS_TEST_DATABASE_URL必须指向以_test结尾的独立测试库")
- if not jwt_secret or not password:
- pytest.skip("缺少B2测试专用密钥或密码环境变量")
- from flask import Flask
- from flask_cors import CORS
- from sqlalchemy import delete, update
- from dms import init_dms
- from dms.extensions import db
- from dms.models import (
- AttachmentBinding,
- AuditLog,
- Category,
- Document,
- Organization,
- Permission,
- User,
- )
- from dms.security.passwords import hash_password
- test_storage = tempfile.TemporaryDirectory(prefix="dms-test-storage-")
- os.environ["DMS_DATABASE_URL"] = database_url
- os.environ["DMS_JWT_SECRET"] = jwt_secret
- os.environ["DMS_STORAGE_ROOT"] = test_storage.name
- app = Flask("dms-b2-test")
- CORS(app)
- init_dms(app)
- app.config.update(TESTING=True)
- with app.app_context():
- db.session.execute(delete(AuditLog))
- db.session.execute(delete(Permission))
- db.session.execute(delete(AttachmentBinding))
- db.session.execute(
- delete(Document).where(Document.document_type == "SUB_PLAN")
- )
- db.session.execute(delete(Document))
- db.session.execute(update(Category).values(parent_id=None))
- db.session.execute(delete(Category))
- db.session.execute(delete(User))
- db.session.execute(update(Organization).values(parent_id=None))
- db.session.execute(delete(Organization))
- db.session.commit()
- root = Organization(
- org_code="ORG_ROOT",
- org_name="机关",
- org_path="/机关",
- sort_no=10,
- status="ENABLED",
- )
- db.session.add(root)
- db.session.flush()
- ops = Organization(
- org_code="ORG_OPS",
- org_name="作战部",
- org_path="/机关/作战部",
- parent_id=root.id,
- sort_no=20,
- status="ENABLED",
- )
- comms = Organization(
- org_code="ORG_COMMS",
- org_name="通信部",
- org_path="/机关/通信部",
- parent_id=root.id,
- sort_no=30,
- status="ENABLED",
- )
- disabled_org = Organization(
- org_code="ORG_DISABLED",
- org_name="停用部门",
- org_path="/机关/停用部门",
- parent_id=root.id,
- sort_no=40,
- status="DISABLED",
- )
- db.session.add_all([ops, comms, disabled_org])
- db.session.flush()
- db.session.add_all(
- [
- User(
- username="admin",
- password_hash=hash_password(password),
- real_name="系统管理员",
- organization_id=root.id,
- organization_name=root.org_name,
- role_code="ADMIN",
- security_level="TOP_SECRET",
- status="ENABLED",
- ),
- User(
- username="auditor",
- password_hash=hash_password(password),
- real_name="审计员",
- organization_id=root.id,
- organization_name=root.org_name,
- role_code="AUDITOR",
- security_level="TOP_SECRET",
- status="ENABLED",
- ),
- User(
- username="user",
- password_hash=hash_password(password),
- real_name="普通用户",
- organization_id=ops.id,
- organization_name=ops.org_name,
- role_code="USER",
- security_level="SECRET",
- status="ENABLED",
- ),
- User(
- username="disabled",
- password_hash=hash_password(password),
- real_name="停用用户",
- organization_id=comms.id,
- organization_name=comms.org_name,
- role_code="USER",
- security_level="SECRET",
- status="DISABLED",
- ),
- ]
- )
- db.session.commit()
- yield app
- with app.app_context():
- db.session.remove()
- test_storage.cleanup()
- @pytest.fixture()
- def b2_client(b2_app):
- return b2_app.test_client()
- @pytest.fixture()
- def b2_password():
- password = os.environ.get("DMS_TEST_USER_PASSWORD")
- if not password:
- pytest.skip("缺少B2测试专用密码环境变量DMS_TEST_USER_PASSWORD")
- return password
- @pytest.fixture()
- def login_token(b2_client, b2_password):
- response = b2_client.post(
- "/api/v1/auth/login",
- json={
- "username": "admin",
- "password": b2_password,
- "keepSignedIn": False,
- },
- )
- assert response.status_code == 200
- return response.get_json()["data"]["accessToken"]
- @pytest.fixture()
- def token_for(b2_client, b2_password):
- def issue(username: str) -> str:
- response = b2_client.post(
- "/api/v1/auth/login",
- json={
- "username": username,
- "password": b2_password,
- "keepSignedIn": False,
- },
- )
- assert response.status_code == 200
- return response.get_json()["data"]["accessToken"]
- return issue
- @pytest.fixture()
- def b3_app(b2_app):
- from dms.extensions import db
- from dms.models import Category
- with b2_app.app_context():
- root_a = Category(
- category_code="ROOT_A",
- category_name="场景根A",
- category_type="SCENE",
- category_path="/场景根A",
- sort_no=10,
- document_count=0,
- status="ENABLED",
- )
- root_b = Category(
- category_code="ROOT_B",
- category_name="场景根B",
- category_type="SCENE",
- category_path="/场景根B",
- sort_no=10,
- document_count=0,
- status="ENABLED",
- )
- root_c = Category(
- category_code="ROOT_C",
- category_name="场景根C",
- category_type="SCENE",
- category_path="/场景根C",
- sort_no=30,
- document_count=0,
- status="ENABLED",
- )
- disabled = Category(
- category_code="DISABLED_ROOT",
- category_name="停用分类",
- category_type="OTHER",
- category_path="/停用分类",
- sort_no=40,
- document_count=0,
- status="DISABLED",
- )
- deleted = Category(
- category_code="DELETED_CODE",
- category_name="已删除分类",
- category_type="OTHER",
- category_path="/已删除分类",
- sort_no=50,
- document_count=0,
- status="ENABLED",
- is_deleted=True,
- )
- db.session.add_all([root_a, root_b, root_c, disabled, deleted])
- db.session.flush()
- db.session.add_all(
- [
- Category(
- category_code="CHILD_A1",
- category_name="样式A1",
- category_type="STYLE",
- parent_id=root_a.id,
- category_path="/场景根A/样式A1",
- sort_no=10,
- document_count=0,
- status="ENABLED",
- ),
- Category(
- category_code="CHILD_A2",
- category_name="样式A2",
- category_type="STYLE",
- parent_id=root_a.id,
- category_path="/场景根A/样式A2",
- sort_no=20,
- document_count=0,
- status="ENABLED",
- ),
- ]
- )
- db.session.commit()
- return b2_app
- @pytest.fixture()
- def b3_client(b3_app):
- return b3_app.test_client()
- @pytest.fixture()
- def b4_app(b3_app):
- from dms.extensions import db
- from dms.models import (
- AttachmentBinding,
- Category,
- Document,
- Organization,
- Permission,
- User,
- )
- def document(
- name: str,
- document_type: str,
- *,
- category: Category | None = None,
- status: str = "PUBLISHED",
- security: str = "INTERNAL",
- visibility: str = "ALL_AUTHENTICATED",
- parent: Document | None = None,
- root: Document | None = None,
- attachment_type: str | None = None,
- deleted: bool = False,
- ) -> Document:
- item = Document(
- document_name=name,
- summary=f"{name}摘要",
- document_type=document_type,
- document_status=status,
- security_level=security,
- visibility_type=visibility,
- attachment_type=attachment_type,
- category_id=category.id if category else None,
- category_name=category.category_name if category else None,
- category_path=category.category_path if category else None,
- parent_document_id=parent.id if parent else None,
- root_document_id=root.id if root else None,
- tags=["B4", "测试"],
- original_file_name=f"{name}.docx",
- file_relative_path=f"original/B4_TEST_{name}.docx",
- file_extension="docx",
- mime_type="application/vnd.openxmlformats-officedocument.wordprocessingml.document",
- file_size=128,
- file_hash="a" * 64,
- search_text=f"{name} B4 测试",
- created_by_name="系统管理员",
- updated_by_name="系统管理员",
- is_deleted=deleted,
- )
- db.session.add(item)
- db.session.flush()
- return item
- with b3_app.app_context():
- category = db.session.scalar(
- db.select(Category).where(Category.category_code == "ROOT_A")
- )
- admin = db.session.scalar(db.select(User).where(User.username == "admin"))
- user = db.session.scalar(db.select(User).where(User.username == "user"))
- root_org = db.session.scalar(
- db.select(Organization).where(Organization.org_code == "ORG_ROOT")
- )
- assert (
- category is not None
- and admin is not None
- and user is not None
- and root_org is not None
- )
- main_all = document("B4_TEST_全部主案", "MAIN", category=category)
- main_org = document(
- "B4_TEST_组织主案",
- "MAIN",
- category=category,
- security="SECRET",
- visibility="ORGANIZATION",
- )
- main_custom = document(
- "B4_TEST_自定义主案",
- "MAIN",
- category=category,
- security="SECRET",
- visibility="CUSTOM",
- )
- document(
- "B4_TEST_草稿主案",
- "MAIN",
- category=category,
- status="DRAFT",
- )
- document(
- "B4_TEST_绝密主案",
- "MAIN",
- category=category,
- security="TOP_SECRET",
- )
- document(
- "B4_TEST_无授权主案",
- "MAIN",
- category=category,
- visibility="CUSTOM",
- )
- sub = document(
- "B4_TEST_动态继承子案",
- "SUB_PLAN",
- category=category,
- parent=main_custom,
- root=main_custom,
- security="SECRET",
- # 故意与根主案不同,验证输出和授权动态继承根主案。
- visibility="ALL_AUTHENTICATED",
- )
- main_custom.child_count = 1
- attachment_one = document(
- "B4_TEST_规范附件",
- "ATTACHMENT",
- security="PUBLIC",
- visibility="ALL_AUTHENTICATED",
- attachment_type="WORK_STANDARD",
- )
- attachment_two = document(
- "B4_TEST_表格附件",
- "ATTACHMENT",
- security="PUBLIC",
- visibility="ALL_AUTHENTICATED",
- attachment_type="TABLE",
- )
- document(
- "B4_TEST_已删除附件",
- "ATTACHMENT",
- security="PUBLIC",
- visibility="ALL_AUTHENTICATED",
- attachment_type="OTHER",
- deleted=True,
- )
- db.session.add_all(
- [
- Permission(
- document_id=main_org.id,
- subject_type="ORG",
- subject_id=root_org.id,
- subject_name=root_org.org_name,
- can_view=True,
- can_download=True,
- created_by=admin.id,
- updated_by=admin.id,
- ),
- Permission(
- document_id=main_custom.id,
- subject_type="USER",
- subject_id=user.id,
- subject_name=user.real_name,
- can_view=True,
- can_download=True,
- created_by=admin.id,
- updated_by=admin.id,
- ),
- AttachmentBinding(
- main_document_id=main_all.id,
- attachment_document_id=attachment_one.id,
- sort_no=20,
- created_by=admin.id,
- updated_by=admin.id,
- ),
- AttachmentBinding(
- main_document_id=main_all.id,
- attachment_document_id=attachment_two.id,
- sort_no=10,
- created_by=admin.id,
- updated_by=admin.id,
- ),
- AttachmentBinding(
- main_document_id=main_org.id,
- attachment_document_id=attachment_one.id,
- sort_no=10,
- created_by=admin.id,
- updated_by=admin.id,
- ),
- ]
- )
- main_all.attachment_count = 2
- main_org.attachment_count = 1
- db.session.commit()
- b3_app.config["B4_IDS"] = {
- "main_all": main_all.id,
- "main_org": main_org.id,
- "main_custom": main_custom.id,
- "sub": sub.id,
- "attachment_one": attachment_one.id,
- "attachment_two": attachment_two.id,
- }
- return b3_app
- @pytest.fixture()
- def b4_client(b4_app):
- return b4_app.test_client()
- @pytest.fixture()
- def b5_app(b3_app):
- return b3_app
- @pytest.fixture()
- def b5_client(b5_app):
- return b5_app.test_client()
- @pytest.fixture()
- def b6_app(b4_app):
- return b4_app
- @pytest.fixture()
- def b6_client(b6_app):
- return b6_app.test_client()
- @pytest.fixture()
- def b7_app(b4_app):
- from datetime import datetime
- from dms.extensions import db
- from dms.models import AuditLog, User
- with b4_app.app_context():
- users = {
- item.username: item
- for item in db.session.scalars(db.select(User)).all()
- }
- def audit(
- username,
- created_at,
- action,
- *,
- result="SUCCESS",
- target_type="DOCUMENT",
- target_id=101,
- target_name="B7目标",
- real_name=None,
- organization_name=None,
- failure_reason=None,
- request_id=None,
- client_ip="127.0.0.1",
- ):
- user = users.get(username) if username else None
- row = AuditLog(
- user_id=user.id if user else None,
- username=username,
- real_name=real_name or (user.real_name if user else None),
- organization_id=user.organization_id if user else None,
- organization_name=(
- organization_name
- or (user.organization_name if user else None)
- ),
- action_type=action,
- target_type=target_type,
- target_id=target_id,
- target_name=target_name,
- operation_result=result,
- failure_reason=failure_reason,
- client_ip=client_ip,
- request_id=request_id,
- created_at=created_at,
- )
- db.session.add(row)
- db.session.flush()
- return row
- rows = [
- audit(
- "admin",
- datetime(2026, 1, 5, 0, 0),
- "VIEW_DOCUMENT",
- real_name="管理员旧名",
- organization_name="旧组织",
- target_name="关键目标",
- request_id="b7-request-alpha",
- ),
- audit(
- "admin",
- datetime(2026, 1, 5, 1, 0),
- "DOWNLOAD_DOCUMENT",
- result="FAILURE",
- failure_reason="关键失败原因",
- request_id="b7-request-beta",
- ),
- audit(
- "auditor",
- datetime(2026, 1, 6, 2, 0),
- "LOGIN",
- target_type="AUTH",
- target_id=users["auditor"].id,
- target_name="auditor",
- request_id="b7-request-gamma",
- ),
- audit(
- "user",
- datetime(2026, 1, 7, 0, 0),
- "VIEW_DOCUMENT",
- target_id=102,
- request_id="b7-request-delta",
- ),
- audit(
- "user",
- datetime(2026, 1, 7, 1, 0),
- "VIEW_DOCUMENT",
- target_id=103,
- request_id="b7-request-epsilon",
- ),
- audit(
- "admin",
- datetime(2026, 1, 7, 1, 0),
- "VIEW_DOCUMENT",
- target_id=104,
- real_name="管理员新名",
- organization_name="新组织",
- request_id="b7-request-zeta",
- ),
- audit(
- "admin",
- datetime(2026, 1, 7, 2, 0),
- "VIEW_DOCUMENT",
- result="FAILURE",
- target_id=105,
- real_name="管理员新名",
- organization_name="新组织",
- request_id="b7-request-eta",
- ),
- audit(
- None,
- datetime(2026, 1, 8, 0, 0),
- "LOGIN",
- result="FAILURE",
- target_type="AUTH",
- target_id=None,
- target_name="unknown",
- failure_reason="用户名不存在",
- request_id="b7-request-theta",
- ),
- audit(
- "auditor",
- datetime(2026, 2, 2, 0, 0),
- "LOGOUT",
- target_type="AUTH",
- target_id=users["auditor"].id,
- target_name="auditor",
- request_id="b7-request-iota",
- ),
- ]
- db.session.commit()
- b4_app.config["B7_AUDIT_IDS"] = [row.id for row in rows]
- b4_app.config["B7_USER_IDS"] = {
- name: item.id for name, item in users.items()
- }
- return b4_app
- @pytest.fixture()
- def b7_client(b7_app):
- return b7_app.test_client()
|