收敛用户角色并共享项目库
- 后端限制系统只保留默认 admin 管理员,新建用户固定为标注员,并拒绝观察员或额外管理员角色。 - 将项目、帧、媒体解析、AI 标注、任务、Dashboard 和导出接口改为共享项目库访问,标注员具备同等项目管理和标注能力。 - 前端用户管理移除角色选择和观察员入口,只展示唯一管理员与标注员状态。 - 更新后端/前端测试,覆盖唯一 admin、旧 viewer 归一为标注员、用户删除和共享项目库访问。 - 同步更新 AGENTS 与 doc 文档中的角色权限、共享项目库和测试计划说明。
This commit is contained in:
@@ -12,14 +12,14 @@ def test_admin_user_management_and_audit_logs(client, db_session):
|
||||
})
|
||||
assert created.status_code == 201
|
||||
user_id = created.json()["id"]
|
||||
assert created.json()["role"] == "annotator"
|
||||
|
||||
updated = client.patch(f"/api/admin/users/{user_id}", json={
|
||||
"role": "viewer",
|
||||
"password": "newsecret",
|
||||
"is_active": False,
|
||||
})
|
||||
assert updated.status_code == 200
|
||||
assert updated.json()["role"] == "viewer"
|
||||
assert updated.json()["role"] == "annotator"
|
||||
assert updated.json()["is_active"] == 0
|
||||
|
||||
users = client.get("/api/admin/users")
|
||||
@@ -37,8 +37,41 @@ def test_admin_user_management_and_audit_logs(client, db_session):
|
||||
assert "admin.user_deleted" in actions
|
||||
|
||||
|
||||
def test_only_default_admin_role_is_supported(client, db_session):
|
||||
extra_admin = client.post("/api/admin/users", json={
|
||||
"username": "chief",
|
||||
"password": "secret123",
|
||||
"role": "admin",
|
||||
"is_active": True,
|
||||
})
|
||||
assert extra_admin.status_code == 400
|
||||
|
||||
viewer = client.post("/api/admin/users", json={
|
||||
"username": "observer",
|
||||
"password": "secret123",
|
||||
"role": "viewer",
|
||||
"is_active": True,
|
||||
})
|
||||
assert viewer.status_code == 400
|
||||
|
||||
created = client.post("/api/admin/users", json={
|
||||
"username": "doctor",
|
||||
"password": "secret123",
|
||||
"is_active": True,
|
||||
})
|
||||
assert created.status_code == 201
|
||||
user_id = created.json()["id"]
|
||||
assert created.json()["role"] == "annotator"
|
||||
assert client.patch(f"/api/admin/users/{user_id}", json={"role": "admin"}).status_code == 400
|
||||
assert client.patch(f"/api/admin/users/{user_id}", json={"role": "viewer"}).status_code == 400
|
||||
|
||||
admin_id = client.get("/api/auth/me").json()["id"]
|
||||
assert client.patch(f"/api/admin/users/{admin_id}", json={"role": "annotator"}).status_code == 400
|
||||
assert client.patch(f"/api/admin/users/{admin_id}", json={"username": "chief"}).status_code == 400
|
||||
|
||||
|
||||
def test_admin_routes_require_admin_role(client, db_session):
|
||||
user = User(username="viewer", password_hash=hash_password("secret123"), role="viewer", is_active=1)
|
||||
user = User(username="doctor", password_hash=hash_password("secret123"), role="annotator", is_active=1)
|
||||
db_session.add(user)
|
||||
db_session.commit()
|
||||
db_session.refresh(user)
|
||||
@@ -51,7 +84,7 @@ def test_admin_routes_require_admin_role(client, db_session):
|
||||
client.headers.update({"Authorization": original_auth})
|
||||
|
||||
|
||||
def test_viewer_role_is_read_only_for_business_mutations(client, db_session):
|
||||
def test_legacy_viewer_role_is_promoted_to_annotator(client, db_session):
|
||||
project = client.post("/api/projects", json={"name": "Readonly Check"}).json()
|
||||
user = User(username="readonly", password_hash=hash_password("secret123"), role="viewer", is_active=1)
|
||||
db_session.add(user)
|
||||
@@ -61,14 +94,14 @@ def test_viewer_role_is_read_only_for_business_mutations(client, db_session):
|
||||
client.headers.update({"Authorization": f"Bearer {create_access_token(user)}"})
|
||||
try:
|
||||
assert client.get("/api/projects").status_code == 200
|
||||
assert client.post("/api/projects", json={"name": "Nope"}).status_code == 403
|
||||
assert client.patch(f"/api/projects/{project['id']}", json={"name": "Nope"}).status_code == 403
|
||||
assert client.post("/api/ai/annotate", json={"project_id": project["id"]}).status_code == 403
|
||||
assert client.post("/api/projects", json={"name": "Annotator Project"}).status_code == 201
|
||||
assert client.patch(f"/api/projects/{project['id']}", json={"name": "Shared Edit"}).status_code == 200
|
||||
assert client.get("/api/auth/me").json()["role"] == "annotator"
|
||||
finally:
|
||||
client.headers.update({"Authorization": original_auth})
|
||||
|
||||
|
||||
def test_admin_cannot_delete_self_or_user_with_projects(client, db_session):
|
||||
def test_admin_cannot_delete_self_but_can_delete_project_author(client, db_session):
|
||||
me = client.get("/api/auth/me").json()
|
||||
assert client.delete(f"/api/admin/users/{me['id']}").status_code == 400
|
||||
|
||||
@@ -80,7 +113,8 @@ def test_admin_cannot_delete_self_or_user_with_projects(client, db_session):
|
||||
db_session.commit()
|
||||
|
||||
response = client.delete(f"/api/admin/users/{user.id}")
|
||||
assert response.status_code == 409
|
||||
assert response.status_code == 204
|
||||
assert db_session.query(Project).filter(Project.name == "Owned").count() == 1
|
||||
|
||||
|
||||
def test_demo_factory_reset_leaves_admin_and_parsed_demo_dicom(client, db_session, monkeypatch, tmp_path):
|
||||
|
||||
Reference in New Issue
Block a user