Files
company-ai-platform/tests/test_v2_hardening.py
JiuContinent eb8267ed18 ```
feat(feishu): 添加飞书入站事件inbox和混合数据库协调功能

- 实现飞书入站事件持久化inbox机制,支持状态管理、租约锁定和重试退避
- 添加混合数据库基线协调工具,确保平台PostgreSQL结构安全对齐
- 增加运行组件心跳检测和readiness就绪检查机制
- 实现app_ticket事件的安全轮换和验证处理
- 添加生产环境运行编排和fail-closed安全机制
- 支持webhook快速确认和长连接独立进程处理
- 完善个人数据擦除时的待处理事件清理功能
```
2026-07-27 17:14:37 +08:00

321 lines
11 KiB
Python

import json
from pathlib import Path
import pytest
from sqlalchemy import create_engine, select
from sqlalchemy.orm import Session, sessionmaker
from app.core.background.task_queue.reports import _queue_celery_report_push
from app.core.config import Settings
from app.core.http.masking import MASKED_VALUE, mask_sensitive
from app.core.utils.time import utc_now
from app.modules.ai_memory.constants import AIMemoryStatus
from app.modules.ai_memory.models import AIMemoryEntry
from app.modules.ai_memory.service import AIMemoryService
from app.modules.audit.constants import AUDIT_REDACTED_VALUE
from app.modules.audit.models import AuditLog
from app.modules.audit.schemas import AuditLogCreate
from app.modules.audit.service import AuditService
from app.modules.business.models import Project, ReportPushRun
from app.modules.events.models import DomainEvent
from app.modules.feishu.service import FeishuService
from app.modules.reports.constants import ReportPushStatus
from app.tasks import celery_app
from app.tasks.reports import _push_report
from app.tools import init_db
def _production_settings(**overrides: object) -> Settings:
values: dict[str, object] = {
"app_env": "production",
"database_url": (
"postgresql+psycopg://app:runtime-database-password-2026@db/app"
),
"api_key": "runtime-service-key-2026-primary",
"audit_api_key": "runtime-audit-key-2026-independent",
"cors_origins": ["https://internal.example.com"],
"debug": False,
"mask_sensitive_responses": True,
"read_only_mode": True,
"feishu_event_transport": "disabled",
}
values.update(overrides)
return Settings(_env_file=None, **values)
def test_production_keys_must_be_enabled_isolated_and_safe() -> None:
settings = _production_settings()
assert settings.api_key == "runtime-service-key-2026-primary"
with pytest.raises(ValueError, match="API_KEY or API_KEYS"):
_production_settings(
api_key=None,
api_keys=[{"key": "disabled-key", "enabled": False}],
)
with pytest.raises(ValueError, match="cannot overlap"):
_production_settings(audit_api_key="runtime-service-key-2026-primary")
with pytest.raises(ValueError, match="MASK_SENSITIVE_RESPONSES"):
_production_settings(mask_sensitive_responses=False)
with pytest.raises(ValueError, match="READ_ONLY_MODE"):
_production_settings(read_only_mode=False)
def test_audit_json_strings_and_responses_mask_sensitive_fields() -> None:
engine = create_engine("sqlite://")
AuditLog.__table__.create(engine)
try:
with Session(engine) as db:
service = AuditService(db)
dict_record = service.log(
AuditLogCreate(
action="security.redaction.dict",
request_payload={
"accessToken": "access-secret",
"nested": {
"clientSecret": "client-secret",
"safe": "visible",
},
},
)
)
json_record = service.log(
AuditLogCreate(
action="security.redaction.json",
request_payload=json.dumps(
{
"authorization": "Bearer secret",
"nested": {
"refreshToken": "refresh-secret",
"safe": "visible",
},
}
),
)
)
dict_stored = json.loads(dict_record.request_payload or "{}")
json_stored = json.loads(json_record.request_payload or "{}")
assert dict_stored["accessToken"] == AUDIT_REDACTED_VALUE
assert dict_stored["nested"]["clientSecret"] == AUDIT_REDACTED_VALUE
assert dict_stored["nested"]["safe"] == "visible"
assert json_stored["authorization"] == AUDIT_REDACTED_VALUE
assert json_stored["nested"]["refreshToken"] == AUDIT_REDACTED_VALUE
assert json_stored["nested"]["safe"] == "visible"
masked = mask_sensitive(
{
"accessToken": "access-secret",
"nested": {
"clientSecret": "client-secret",
"privateKey": "private-secret",
"safe": "visible",
},
}
)
assert masked["accessToken"] == MASKED_VALUE
assert masked["nested"]["clientSecret"] == MASKED_VALUE
assert masked["nested"]["privateKey"] == MASKED_VALUE
assert masked["nested"]["safe"] == "visible"
finally:
engine.dispose()
def test_report_queue_failure_is_persisted(
monkeypatch: pytest.MonkeyPatch,
) -> None:
engine = create_engine("sqlite://")
ReportPushRun.__table__.create(engine)
factory = sessionmaker(bind=engine, expire_on_commit=False)
class BrokenSignature:
def apply_async(self, task_id: str) -> None:
_ = task_id
raise RuntimeError("broker unavailable")
monkeypatch.setattr("app.core.database.SessionLocal", factory)
monkeypatch.setattr(
celery_app,
"signature",
lambda *args, **kwargs: BrokenSignature(),
)
try:
with pytest.raises(RuntimeError, match="broker unavailable"):
_queue_celery_report_push(
task_name="reports.test",
report_type="test",
title="Test report",
receive_id=None,
receive_id_type="chat_id",
actor="pytest",
)
with factory() as db:
run = db.execute(select(ReportPushRun)).scalar_one()
assert run.status == ReportPushStatus.FAILED
assert run.task_id
assert run.error_message == "broker unavailable"
finally:
engine.dispose()
def test_eager_report_terminal_state_is_not_overwritten(
monkeypatch: pytest.MonkeyPatch,
) -> None:
engine = create_engine("sqlite://")
ReportPushRun.__table__.create(engine)
factory = sessionmaker(bind=engine, expire_on_commit=False)
class EagerSignature:
def __init__(self, push_run_code: str):
self.push_run_code = push_run_code
def apply_async(self, task_id: str) -> None:
with factory() as db:
run = db.execute(
select(ReportPushRun).where(
ReportPushRun.code == self.push_run_code
)
).scalar_one()
assert run.task_id == task_id
run.status = ReportPushStatus.SUCCESS
db.commit()
def eager_signature(*args: object, **kwargs: object) -> EagerSignature:
_ = args
task_kwargs = kwargs["kwargs"]
assert isinstance(task_kwargs, dict)
return EagerSignature(str(task_kwargs["push_run_code"]))
monkeypatch.setattr("app.core.database.SessionLocal", factory)
monkeypatch.setattr(celery_app, "signature", eager_signature)
try:
queued = _queue_celery_report_push(
task_name="reports.test",
report_type="test",
title="Test report",
receive_id=None,
receive_id_type="chat_id",
actor="pytest",
)
with factory() as db:
run = db.execute(select(ReportPushRun)).scalar_one()
assert run.status == ReportPushStatus.SUCCESS
assert run.task_id == queued["task_id"]
finally:
engine.dispose()
def test_successful_report_task_redelivery_does_not_send_twice(
monkeypatch: pytest.MonkeyPatch,
) -> None:
engine = create_engine("sqlite://")
for table in (AuditLog.__table__, DomainEvent.__table__, ReportPushRun.__table__):
table.create(engine)
factory = sessionmaker(bind=engine, expire_on_commit=False)
calls = {"build": 0, "send": 0}
def build_report(service: object, actor: str) -> dict[str, object]:
_ = service, actor
calls["build"] += 1
return {
"title": "Idempotent report",
"report_type": "test",
"lines": ["ok"],
"content": "ok",
}
def fake_send_card(
service: object,
card: dict,
receive_id: str | None,
receive_id_type: str,
actor: str,
) -> dict[str, object]:
_ = service, card, receive_id, receive_id_type, actor
calls["send"] += 1
return {"code": 0, "message_id": "om-idempotent"}
monkeypatch.setattr("app.tasks.reports.SessionLocal", factory)
monkeypatch.setattr(FeishuService, "send_card", fake_send_card)
try:
with factory() as db:
run = ReportPushRun(
code="PUSH-IDEMPOTENT",
report_type="test",
title="Idempotent report",
receive_id="oc-idempotent",
receive_id_type="chat_id",
status=ReportPushStatus.QUEUED,
actor="pytest",
)
db.add(run)
db.commit()
first = _push_report(build_report, "oc-idempotent", "chat_id", "pytest", run.code)
second = _push_report(build_report, "oc-idempotent", "chat_id", "pytest", run.code)
assert first == second == {"code": 0, "message_id": "om-idempotent"}
assert calls == {"build": 1, "send": 1}
with factory() as db:
stored = db.execute(select(ReportPushRun)).scalar_one()
assert stored.status == ReportPushStatus.SUCCESS
assert stored.sent_at is not None
finally:
engine.dispose()
def test_init_db_upgrades_to_alembic_head(
monkeypatch: pytest.MonkeyPatch,
) -> None:
called: dict[str, object] = {}
def fake_upgrade(config: object, revision: str) -> None:
called["config"] = config
called["revision"] = revision
monkeypatch.setattr(init_db.command, "upgrade", fake_upgrade)
init_db.main()
config = called["config"]
assert called["revision"] == "head"
assert Path(config.config_file_name).name == "alembic.ini"
assert Path(config.get_main_option("script_location")).name == "alembic"
def test_memory_retention_does_not_commit_caller_transaction() -> None:
engine = create_engine("sqlite://")
AIMemoryEntry.__table__.create(engine)
Project.__table__.create(engine)
factory = sessionmaker(bind=engine, expire_on_commit=False)
try:
with factory() as db:
expired = AIMemoryEntry(
code="MEM-EXPIRED",
scope="project",
subject="P-ROLLBACK",
content="expired",
status=AIMemoryStatus.ACTIVE,
expires_at=utc_now(),
)
db.add(expired)
db.commit()
db.add(Project(code="P-ROLLBACK", name="Must roll back"))
assert AIMemoryService(db).list_entries(scope="project") == []
db.rollback()
with factory() as db:
assert db.execute(
select(Project).where(Project.code == "P-ROLLBACK")
).scalar_one_or_none() is None
stored = db.execute(
select(AIMemoryEntry).where(AIMemoryEntry.code == "MEM-EXPIRED")
).scalar_one()
assert stored.status == AIMemoryStatus.ACTIVE
finally:
engine.dispose()