fix(台账): 更新合同模板并恢复文档服务器备份

This commit is contained in:
Cheng Zhou
2026-09-04 09:49:51 +08:00
parent 5eb50c704f
commit 49b32dc20f
6 changed files with 215 additions and 6 deletions
@@ -316,6 +316,74 @@ def test_result_download_url_is_restricted_and_public_proxy_urls_are_rewritten(m
onlyoffice_collaboration_service._validate_result_url(value)
@pytest.mark.asyncio
async def test_forgotten_document_command_downloads_and_validates_server_backup(monkeypatch):
key = "ctms-collab-forgotten-key"
recovered = collaboration_service.blank_file_bytes("cell")
request = {}
class FakeResponse:
status_code = 200
@staticmethod
def json():
return {"error": 0, "key": key, "url": "http://onlyoffice/cache/forgotten.xlsx"}
class FakeClient:
async def __aenter__(self):
return self
async def __aexit__(self, *_args):
return None
async def post(self, url, *, params, json):
request.update(url=url, params=params, body=json)
return FakeResponse()
monkeypatch.setattr(onlyoffice_collaboration_service.httpx, "AsyncClient", lambda **_kwargs: FakeClient())
download = AsyncMock(return_value=recovered)
monkeypatch.setattr(onlyoffice_collaboration_service, "_download_result", download)
result = await onlyoffice_collaboration_service._recover_forgotten_content(key, "cell")
assert result == recovered
assert request["url"] == "http://onlyoffice/command"
assert request["params"] == {"shardkey": key}
assert jwt.decode(
request["body"]["token"], settings.ONLYOFFICE_JWT_SECRET, algorithms=["HS256"]
) == {"c": "getForgotten", "key": key}
download.assert_awaited_once_with("http://onlyoffice/cache/forgotten.xlsx")
@pytest.mark.asyncio
async def test_forgotten_document_command_rejects_a_damaged_backup(monkeypatch):
key = "ctms-collab-damaged-key"
class FakeResponse:
status_code = 200
@staticmethod
def json():
return {"error": 0, "key": key, "url": "http://onlyoffice/cache/damaged.xlsx"}
class FakeClient:
async def __aenter__(self):
return self
async def __aexit__(self, *_args):
return None
async def post(self, *_args, **_kwargs):
return FakeResponse()
monkeypatch.setattr(onlyoffice_collaboration_service.httpx, "AsyncClient", lambda **_kwargs: FakeClient())
monkeypatch.setattr(onlyoffice_collaboration_service, "_download_result", AsyncMock(return_value=b"broken"))
with pytest.raises(HTTPException) as error:
await onlyoffice_collaboration_service._recover_forgotten_content(key, "cell")
assert error.value.status_code == 502
@pytest.mark.asyncio
async def test_editor_config_grants_edit_only_after_collaboration_permission(monkeypatch, tmp_path):
user_id = uuid.uuid4()
@@ -1016,6 +1084,31 @@ async def test_callback_error_status_is_recorded_and_acknowledged():
assert db.added[0].result == "ERROR"
@pytest.mark.asyncio
async def test_force_save_error_keeps_the_live_session_active():
session = SimpleNamespace(
id=uuid.uuid4(),
file_id=uuid.uuid4(),
document_key="ctms-collab-force-save-error-key",
generation=1,
status="ACTIVE",
active_users=None,
last_callback_at=None,
)
item = SimpleNamespace(id=session.file_id, generation=1)
db = _CallbackDb(session, item)
result = await onlyoffice_collaboration_service.process_callback(
db,
session.id,
CollaborationCallbackPayload(key=session.document_key, status=7),
)
assert result == {"error": 0}
assert session.status == "ACTIVE"
assert db.added[0].result == "ERROR"
@pytest.mark.asyncio
async def test_force_save_updates_session_recovery_revision(monkeypatch):
session = SimpleNamespace(
+35
View File
@@ -86,6 +86,41 @@ async def test_initialization_preserves_both_uploaded_templates_and_is_idempoten
assert "ledgers" in Path(revision.file_uri).parts
@pytest.mark.asyncio
async def test_failed_ledger_session_recovers_server_backup_under_a_new_document_key(env, monkeypatch):
item = await env.db.get(CollaborationFile, env.initial[0].id)
first = await office.build_editor_config(env.db, item, env.admin)
failed = await env.db.scalar(select(CollaborationSession).where(
CollaborationSession.file_id == item.id,
CollaborationSession.generation == item.generation,
))
failed.status = "ERROR"
await env.db.commit()
current = await env.db.get(CollaborationRevision, item.current_revision_id)
recovered_buffer = io.BytesIO(Path(current.file_uri).read_bytes())
with zipfile.ZipFile(recovered_buffer, "a") as package:
package.comment = b"document-server-recovery"
recovery = AsyncMock(return_value=recovered_buffer.getvalue())
monkeypatch.setattr(office, "_recover_forgotten_content", recovery)
reopened = await office.build_editor_config(env.db, item, env.admin)
await env.db.refresh(item)
await env.db.refresh(failed)
sessions = (await env.db.scalars(select(CollaborationSession).where(
CollaborationSession.file_id == item.id,
).order_by(CollaborationSession.generation))).all()
recovered_revision = await env.db.get(CollaborationRevision, item.current_revision_id)
assert first.config["document"]["key"] != reopened.config["document"]["key"]
assert item.generation == 2
assert failed.status == "RECOVERED"
assert [session.generation for session in sessions] == [1, 2]
assert recovered_revision.source == "SERVER_RECOVERY"
assert recovered_revision.change_summary == "自动恢复在线文档服务器备份"
recovery.assert_awaited_once_with(failed.document_key, "cell")
@pytest.mark.asyncio
async def test_account_grants_are_independent_and_all_file_routes_require_access(env):
await grant(env, env.editor, "EDITOR")