This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
import json
|
||||
|
||||
import pytest
|
||||
from django.contrib.auth.models import User
|
||||
from django.test import Client
|
||||
|
||||
from finance.models import Audit, Investigation, Membership
|
||||
|
||||
|
||||
def test_api_auth_method_and_request_shape_guards(workspace):
|
||||
ds, _, client, *_ = workspace
|
||||
anonymous = Client()
|
||||
assert anonymous.get(f"/api/datasets/{ds.pk}").status_code == 401
|
||||
assert client.get(f"/api/datasets/{ds.pk}/query").status_code == 405
|
||||
assert (
|
||||
client.post(f"/api/datasets/{ds.pk}/query", data="[]", content_type="application/json").status_code
|
||||
== 400
|
||||
)
|
||||
assert (
|
||||
client.post(f"/api/datasets/{ds.pk}/query", data="{}", content_type="text/plain").status_code == 400
|
||||
)
|
||||
bad_login = anonymous.post(
|
||||
"/api/login", data=json.dumps({"username": "x", "password": "wrong"}), content_type="application/json"
|
||||
)
|
||||
assert bad_login.status_code == 401
|
||||
assert anonymous.post("/api/logout").status_code == 401
|
||||
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
|
||||
def test_session_login_logout_and_health_contract(workspace):
|
||||
ds, _, _, _, _ = workspace
|
||||
client = Client()
|
||||
assert client.get("/api/health").json() == {"status": "ok"}
|
||||
session = client.get("/api/session").json()
|
||||
assert session["user"] is None
|
||||
login = client.post(
|
||||
"/api/login",
|
||||
data=json.dumps({"username": ds.created_by.username, "password": "Finance-TestPassword991!"}),
|
||||
content_type="application/json",
|
||||
)
|
||||
assert login.status_code == 200
|
||||
assert client.get("/api/session").json()["user"]["role"] == "admin"
|
||||
assert client.post("/api/logout").json() == {"ok": True}
|
||||
assert client.get("/api/session").json()["user"] is None
|
||||
assert Audit.objects.filter(action__in=["auth.login", "auth.logout"]).count() == 2
|
||||
|
||||
|
||||
def test_user_management_enforces_org_scope_and_self_protection(workspace):
|
||||
ds, _, client, _, _ = workspace
|
||||
base = {"role": "analyst", "departments": ["儿科"]}
|
||||
created = client.post(
|
||||
"/api/users",
|
||||
data=json.dumps({**base, "username": "managed-user", "password": "Managed-TestPassword991!"}),
|
||||
content_type="application/json",
|
||||
)
|
||||
assert created.status_code == 200
|
||||
managed = User.objects.get(username="managed-user")
|
||||
membership = Membership.objects.get(user=managed)
|
||||
assert membership.organization == ds.organization
|
||||
assert membership.departments == ["儿科"]
|
||||
|
||||
denied = client.post(
|
||||
"/api/users",
|
||||
data=json.dumps({**base, "username": "weak-user", "password": "123"}),
|
||||
content_type="application/json",
|
||||
)
|
||||
assert denied.status_code == 400
|
||||
assert (
|
||||
client.patch(
|
||||
"/api/users",
|
||||
data=json.dumps({"id": ds.created_by_id, "active": False, **base}),
|
||||
content_type="application/json",
|
||||
).status_code
|
||||
== 400
|
||||
)
|
||||
assert (
|
||||
client.patch(
|
||||
"/api/users",
|
||||
data=json.dumps({"id": managed.pk, "active": False, "role": "viewer", "departments": []}),
|
||||
content_type="application/json",
|
||||
).status_code
|
||||
== 200
|
||||
)
|
||||
managed.refresh_from_db()
|
||||
membership.refresh_from_db()
|
||||
assert not managed.is_active
|
||||
assert membership.role == "viewer"
|
||||
|
||||
assert client.get("/api/users").status_code == 200
|
||||
assert client.get("/api/audit").status_code == 200
|
||||
assert Audit.objects.filter(action="user.update").count() == 2
|
||||
|
||||
|
||||
def test_investigation_lifecycle_and_viewer_write_denial(workspace):
|
||||
ds, _, client, _, _ = workspace
|
||||
context = {"start": "2026-08", "end": "2026-08", "comparison": "yoy", "group": "department"}
|
||||
url = f"/api/datasets/{ds.pk}/investigations"
|
||||
saved = client.post(
|
||||
url,
|
||||
data=json.dumps({**context, "status": "reviewing", "note": "复核收入成本"}),
|
||||
content_type="application/json",
|
||||
)
|
||||
assert saved.status_code == 200
|
||||
assert Investigation.objects.filter(dataset=ds, status="reviewing", note="复核收入成本").exists()
|
||||
assert client.get(url).json()["items"][0]["status"] == "reviewing"
|
||||
invalid = client.post(
|
||||
url,
|
||||
data=json.dumps({**context, "status": "closed", "note": ""}),
|
||||
content_type="application/json",
|
||||
)
|
||||
assert invalid.status_code == 400
|
||||
|
||||
viewer = User.objects.create_user("finance-viewer", password="Finance-TestPassword991!")
|
||||
Membership.objects.create(user=viewer, organization=ds.organization, role="viewer")
|
||||
readonly = Client()
|
||||
assert readonly.login(username=viewer.username, password="Finance-TestPassword991!")
|
||||
denied = readonly.post(
|
||||
url,
|
||||
data=json.dumps({**context, "status": "resolved", "note": ""}),
|
||||
content_type="application/json",
|
||||
)
|
||||
assert denied.status_code == 403
|
||||
|
||||
|
||||
def test_evidence_rejects_bad_pagination_and_exports_exact_xlsx(workspace):
|
||||
ds, _, client, *_ = workspace
|
||||
url = f"/api/datasets/{ds.pk}/evidence"
|
||||
invalid = client.post(
|
||||
url,
|
||||
data=json.dumps({"start": "2026-08", "end": "2026-08", "limit": 201}),
|
||||
content_type="application/json",
|
||||
)
|
||||
assert invalid.status_code == 400
|
||||
exported = client.post(
|
||||
f"/api/datasets/{ds.pk}/export",
|
||||
data=json.dumps({"start": "2026-08", "end": "2026-08", "format": "xlsx", "group": "department"}),
|
||||
content_type="application/json",
|
||||
)
|
||||
assert exported.status_code == 200
|
||||
assert exported.streaming
|
||||
assert exported["Content-Disposition"].endswith('"traceiq-evidence.xlsx"')
|
||||
assert b"".join(exported.streaming_content).startswith(b"PK")
|
||||
Reference in New Issue
Block a user