feat: 增加操作审计与受控下载

This commit is contained in:
shenlei
2026-07-22 18:12:19 +09:00
parent 624f534b0f
commit 4dccc4d96b
15 changed files with 323 additions and 9 deletions
+47
View File
@@ -0,0 +1,47 @@
"""操作审计日志接口测试。"""
from unittest.mock import AsyncMock, patch
@patch("backend.services.build_queue.build_queue")
def test_build_creation_is_recorded_and_visible_to_admin(mock_queue, client, tmp_config):
mock_queue.submit = AsyncMock()
response = client.post("/api/tasks", json={"app_id": "1", "scheme_id": "1", "build_type": "Ad_Hoc"})
assert response.status_code == 200
summary = client.get("/api/operations/summary")
assert summary.status_code == 200
assert summary.json()["action_counts"]["build_created"] == 1
logs = client.get("/api/operations").json()
assert logs[0]["username"] == "admin"
assert logs[0]["action"] == "build_created"
def test_download_link_is_recorded(client, tmp_config):
from backend.database import SessionLocal
from backend.models import Task
db = SessionLocal()
db.add(Task(id="download-task", app_id="1", app_name="测试App", build_type="App_Store", scheme_id="1", scheme_name="readoor31", status="completed", oss_url="https://files.example.com/app.ipa"))
db.commit()
db.close()
with patch("backend.services.distribution.get_published_download_url", return_value="https://signed.example.com/app.ipa?signature=1"):
response = client.get("/api/tasks/download-task/download-link")
assert response.status_code == 200
assert "signature=1" in response.json()["url"]
assert client.get("/api/operations").json()[0]["action"] == "ipa_download_requested"
def test_regular_user_cannot_view_operation_logs(client):
from backend.database import SessionLocal
from backend.models import User
from backend.security import hash_password
db = SessionLocal()
db.add(User(id="regular-user", username="reader", password_hash=hash_password("reader-password"), is_admin=False))
db.commit()
db.close()
login = client.post("/api/auth/login", json={"username": "reader", "password": "reader-password"})
client.headers.update({"Authorization": f"Bearer {login.json()['token']}"})
assert client.get("/api/operations").status_code == 403
assert client.get("/api/operations/summary").status_code == 403
+20
View File
@@ -9,6 +9,7 @@ from backend.services.distribution import (
_write_download_page,
_write_manifest,
publish_ipa,
get_published_download_url,
)
@@ -119,3 +120,22 @@ def test_delete_uses_saved_url_for_legacy_artifact_name():
"readoor/iOS/100_2_0_0_0.html",
"readoor/iOS/100_2_0_0_0.png",
]
def test_oss_download_url_is_short_lived_and_uses_actual_object_key(monkeypatch):
class FakeBucket:
def __init__(self, *_args, **_kwargs): pass
def sign_url(self, method, key, expires, **kwargs):
assert (method, key, expires, kwargs) == ("GET", "readoor/iOS/100_2_0_0_0.ipa", 600, {"slash_safe": True})
return "https://signed.example.com/app.ipa?signature=1"
class FakeOss:
Auth = staticmethod(lambda *_args: object())
Bucket = FakeBucket
monkeypatch.setitem(__import__("sys").modules, "oss2", FakeOss)
url = get_published_download_url(
{"BUILD_TYPE": "App_Store"},
{"mode": "oss", "oss": {"access_key_id": "id", "access_key_secret": "secret", "endpoint": "oss.example.com", "bucket_name": "bucket", "base_url": "https://files.example.com"}},
"https://files.example.com/readoor/iOS/100_2_0_0_0.ipa",
)
assert "signature=1" in url