test_market_mobile_permissions.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784
  1. import asyncio
  2. import pytest
  3. from fastapi import HTTPException
  4. from sqlalchemy import create_engine
  5. from sqlalchemy.orm import sessionmaker
  6. from app.core.mobile_menu_guard import (
  7. require_market_menu_code,
  8. require_market_menu_permission,
  9. user_has_tenant_menu_perm,
  10. )
  11. from app.models.base import Base
  12. from app.models.models import (
  13. BizPresignOrder,
  14. BizPresignOrderSampleType,
  15. BizPresignTransfer,
  16. SysMenu,
  17. SysDept,
  18. SysPost,
  19. SysPostRole,
  20. SysRole,
  21. SysRoleDataScopeDetail,
  22. SysRoleMenu,
  23. SysUser,
  24. SysUserPost,
  25. SysUserRole,
  26. )
  27. def _session():
  28. engine = create_engine("sqlite:///:memory:")
  29. Base.metadata.create_all(
  30. engine,
  31. tables=[
  32. SysMenu.__table__,
  33. SysDept.__table__,
  34. SysPost.__table__,
  35. SysPostRole.__table__,
  36. SysRole.__table__,
  37. SysRoleDataScopeDetail.__table__,
  38. SysRoleMenu.__table__,
  39. SysUser.__table__,
  40. SysUserPost.__table__,
  41. SysUserRole.__table__,
  42. BizPresignOrder.__table__,
  43. BizPresignOrderSampleType.__table__,
  44. BizPresignTransfer.__table__,
  45. ],
  46. )
  47. return sessionmaker(bind=engine)()
  48. def _add_role_menu(
  49. db,
  50. *,
  51. tenant_no,
  52. role_id,
  53. menu_id,
  54. perm_code,
  55. role_name="市场角色",
  56. data_scope="1",
  57. ):
  58. db.add(
  59. SysRole(
  60. id=role_id,
  61. role_name=role_name,
  62. data_scope=data_scope,
  63. status="1",
  64. tenant_no=tenant_no,
  65. create_dept_id="",
  66. del_flag=0,
  67. )
  68. )
  69. db.add(
  70. SysMenu(
  71. id=menu_id,
  72. menu_name=perm_code,
  73. parent_id="root",
  74. menu_type="C",
  75. path=f"/{menu_id}",
  76. perms=perm_code,
  77. visible="1",
  78. status="1",
  79. tenant_no=tenant_no,
  80. create_dept_id="",
  81. del_flag=0,
  82. )
  83. )
  84. db.add(
  85. SysRoleMenu(
  86. id=f"rm-{role_id}-{menu_id}",
  87. role_id=role_id,
  88. menu_id=menu_id,
  89. tenant_no=tenant_no,
  90. create_dept_id="",
  91. del_flag=0,
  92. )
  93. )
  94. def test_specialist_permission_does_not_grant_manager_access():
  95. assert not require_market_menu_permission(
  96. user_id="u1",
  97. tenant_no="t1",
  98. required="mini:market:manager:presign",
  99. granted={"mini:market:specialist:presign"},
  100. )
  101. def test_tenant_menu_permission_does_not_fall_back_to_default_tenant():
  102. db = _session()
  103. _add_role_menu(
  104. db,
  105. tenant_no="tenant-a",
  106. role_id="role-a",
  107. menu_id="menu-a",
  108. perm_code="mini:market:manager:presign",
  109. )
  110. _add_role_menu(
  111. db,
  112. tenant_no="tenant-b",
  113. role_id="role-b",
  114. menu_id="menu-b",
  115. perm_code="mini:market:specialist:presign",
  116. )
  117. db.add_all(
  118. [
  119. SysUserRole(
  120. id="ur-a",
  121. user_id="u1",
  122. role_id="role-a",
  123. tenant_no="tenant-a",
  124. create_dept_id="",
  125. del_flag=0,
  126. ),
  127. SysUserRole(
  128. id="ur-b",
  129. user_id="u1",
  130. role_id="role-b",
  131. tenant_no="tenant-b",
  132. create_dept_id="",
  133. del_flag=0,
  134. ),
  135. ]
  136. )
  137. db.commit()
  138. assert user_has_tenant_menu_perm(
  139. db,
  140. user_id="u1",
  141. tenant_no="tenant-b",
  142. perm_code="mini:market:specialist:presign",
  143. )
  144. assert not user_has_tenant_menu_perm(
  145. db,
  146. user_id="u1",
  147. tenant_no="tenant-b",
  148. perm_code="mini:market:manager:presign",
  149. )
  150. def test_tenant_menu_permission_includes_post_inherited_roles():
  151. db = _session()
  152. _add_role_menu(
  153. db,
  154. tenant_no="tenant-b",
  155. role_id="role-b",
  156. menu_id="menu-b",
  157. perm_code="mini:market:specialist:presign",
  158. )
  159. db.add_all(
  160. [
  161. SysPost(
  162. id="post-b",
  163. post_name="市场专员",
  164. post_code="specialist",
  165. status="1",
  166. tenant_no="tenant-b",
  167. create_dept_id="",
  168. del_flag=0,
  169. ),
  170. SysUserPost(
  171. id="up-b",
  172. user_id="u1",
  173. post_id="post-b",
  174. tenant_no="tenant-b",
  175. create_dept_id="",
  176. del_flag=0,
  177. ),
  178. SysPostRole(
  179. id="pr-b",
  180. post_id="post-b",
  181. role_id="role-b",
  182. tenant_no="tenant-b",
  183. create_dept_id="",
  184. del_flag=0,
  185. ),
  186. ]
  187. )
  188. db.commit()
  189. assert user_has_tenant_menu_perm(
  190. db,
  191. user_id="u1",
  192. tenant_no="tenant-b",
  193. perm_code="mini:market:specialist:presign",
  194. )
  195. def test_strict_guard_rejects_missing_tenant_identity():
  196. dependency = require_market_menu_code("mini:market:specialist:presign")
  197. with pytest.raises(HTTPException) as exc_info:
  198. asyncio.run(dependency(user={"user_id": "u1", "tenant_no": ""}, db=None))
  199. assert exc_info.value.status_code == 403
  200. assert exc_info.value.detail == "用户租户或身份信息缺失"
  201. def test_strict_guard_does_not_trust_cross_tenant_super_admin_role():
  202. db = _session()
  203. db.add_all(
  204. [
  205. SysRole(
  206. id="tenant-b-role",
  207. role_name="市场专员",
  208. status="1",
  209. tenant_no="tenant-b",
  210. create_dept_id="",
  211. del_flag=0,
  212. ),
  213. SysUserRole(
  214. id="tenant-b-user-role",
  215. user_id="u1",
  216. role_id="tenant-b-role",
  217. tenant_no="tenant-b",
  218. create_dept_id="",
  219. del_flag=0,
  220. ),
  221. ]
  222. )
  223. db.commit()
  224. dependency = require_market_menu_code("mini:market:specialist:presign")
  225. with pytest.raises(HTTPException) as exc_info:
  226. asyncio.run(
  227. dependency(
  228. user={
  229. "user_id": "u1",
  230. "tenant_no": "tenant-b",
  231. "role": "super_admin",
  232. },
  233. db=db,
  234. )
  235. )
  236. assert exc_info.value.status_code == 403
  237. assert exc_info.value.detail == "无移动端菜单权限"
  238. def test_strict_guard_uses_current_tenant_super_admin_role():
  239. db = _session()
  240. db.add_all(
  241. [
  242. SysRole(
  243. id="tenant-b-super-role",
  244. role_name="超级管理员",
  245. status="1",
  246. tenant_no="tenant-b",
  247. create_dept_id="",
  248. del_flag=0,
  249. ),
  250. SysUserRole(
  251. id="tenant-b-super-user-role",
  252. user_id="u1",
  253. role_id="tenant-b-super-role",
  254. tenant_no="tenant-b",
  255. create_dept_id="",
  256. del_flag=0,
  257. ),
  258. ]
  259. )
  260. db.commit()
  261. dependency = require_market_menu_code("mini:market:specialist:presign")
  262. user = {"user_id": "u1", "tenant_no": "tenant-b", "role": "admin"}
  263. result = asyncio.run(dependency(user=user, db=db))
  264. assert result is user
  265. def _add_auth_user(db, *, tenant_no="tenant-b"):
  266. db.add(
  267. SysUser(
  268. id="u1",
  269. user_no="u1",
  270. nick_name="租户用户",
  271. password="unused",
  272. status="1",
  273. tenant_no=tenant_no,
  274. create_dept_id="dept-b",
  275. del_flag=0,
  276. )
  277. )
  278. def _add_cross_tenant_roles(db):
  279. from app.core.config import settings
  280. default_tenant = settings.DEFAULT_TENANT_NO
  281. _add_role_menu(
  282. db,
  283. tenant_no=default_tenant,
  284. role_id="default-super",
  285. menu_id="default-menu",
  286. perm_code="default:anything",
  287. role_name="超级管理员",
  288. data_scope="1",
  289. )
  290. _add_role_menu(
  291. db,
  292. tenant_no="tenant-b",
  293. role_id="tenant-b-specialist",
  294. menu_id="tenant-b-presign",
  295. perm_code="mini:market:specialist:presign",
  296. role_name="市场专员",
  297. data_scope="4",
  298. )
  299. db.add_all(
  300. [
  301. SysUserRole(
  302. id="default-user-role",
  303. user_id="u1",
  304. role_id="default-super",
  305. tenant_no=default_tenant,
  306. create_dept_id="",
  307. del_flag=0,
  308. ),
  309. SysUserRole(
  310. id="tenant-b-user-role",
  311. user_id="u1",
  312. role_id="tenant-b-specialist",
  313. tenant_no="tenant-b",
  314. create_dept_id="",
  315. del_flag=0,
  316. ),
  317. ]
  318. )
  319. def _add_presign(db, order_id, *, create_by):
  320. db.add(
  321. BizPresignOrder(
  322. id=order_id,
  323. mother_name=order_id,
  324. sales_user_id="u1",
  325. order_status="active",
  326. tenant_no="tenant-b",
  327. create_by=create_by,
  328. create_dept_id="dept-b",
  329. del_flag=0,
  330. )
  331. )
  332. def _authenticate(db, monkeypatch):
  333. from app.core import auth
  334. monkeypatch.setattr(
  335. auth,
  336. "decode_token",
  337. lambda token: {"sub": "u1", "sid": "session-u1"},
  338. )
  339. async def valid_session(session_id):
  340. return session_id == "session-u1"
  341. async def extend_session(session_id):
  342. return None
  343. monkeypatch.setattr(auth, "is_session_valid", valid_session)
  344. monkeypatch.setattr(auth, "extend_session", extend_session)
  345. return asyncio.run(auth.get_current_user_by_token("token", db))
  346. def test_authenticated_tenant_role_drives_menu_and_presign_data_scope(
  347. monkeypatch,
  348. ):
  349. from app.services.market_field_scope import MarketFieldContext
  350. from app.services.market_presign import list_presign_orders
  351. db = _session()
  352. _add_auth_user(db)
  353. _add_cross_tenant_roles(db)
  354. _add_presign(db, "own", create_by="u1")
  355. _add_presign(db, "field-only", create_by="other")
  356. db.commit()
  357. current_user = _authenticate(db, monkeypatch)
  358. dependency = require_market_menu_code("mini:market:specialist:presign")
  359. guarded_user = asyncio.run(dependency(user=current_user, db=db))
  360. rows, total = list_presign_orders(
  361. db,
  362. ctx=MarketFieldContext("tenant-b", "u1", "specialist"),
  363. user_info=guarded_user,
  364. )
  365. assert current_user["tenant_no"] == "tenant-b"
  366. assert current_user["role"] == "admin"
  367. assert current_user["role_names"] == ["市场专员"]
  368. assert total == 1
  369. assert [row["id"] for row in rows] == ["own"]
  370. def test_authenticated_current_tenant_super_admin_remains_unrestricted(
  371. monkeypatch,
  372. ):
  373. from app.services.market_field_scope import MarketFieldContext
  374. from app.services.market_presign import list_presign_orders
  375. db = _session()
  376. _add_auth_user(db)
  377. db.add_all(
  378. [
  379. SysRole(
  380. id="tenant-b-super",
  381. role_name="超级管理员",
  382. data_scope="1",
  383. status="1",
  384. tenant_no="tenant-b",
  385. create_dept_id="",
  386. del_flag=0,
  387. ),
  388. SysUserRole(
  389. id="tenant-b-super-user-role",
  390. user_id="u1",
  391. role_id="tenant-b-super",
  392. tenant_no="tenant-b",
  393. create_dept_id="",
  394. del_flag=0,
  395. ),
  396. ]
  397. )
  398. _add_presign(db, "own", create_by="u1")
  399. _add_presign(db, "field-only", create_by="other")
  400. db.commit()
  401. current_user = _authenticate(db, monkeypatch)
  402. dependency = require_market_menu_code("mini:market:specialist:presign")
  403. guarded_user = asyncio.run(dependency(user=current_user, db=db))
  404. rows, total = list_presign_orders(
  405. db,
  406. ctx=MarketFieldContext("tenant-b", "u1", "specialist"),
  407. user_info=guarded_user,
  408. )
  409. assert current_user["role"] == "super_admin"
  410. assert current_user["role_names"] == ["超级管理员"]
  411. assert total == 2
  412. assert {row["id"] for row in rows} == {"own", "field-only"}
  413. def test_token_issuance_uses_the_users_current_tenant_roles(monkeypatch):
  414. from app.services import issue_user_tokens
  415. db = _session()
  416. _add_auth_user(db)
  417. _add_cross_tenant_roles(db)
  418. db.commit()
  419. async def no_op(**kwargs):
  420. return None
  421. monkeypatch.setattr(issue_user_tokens, "store_session", no_op)
  422. monkeypatch.setattr(issue_user_tokens, "store_refresh_token", no_op)
  423. issued = asyncio.run(
  424. issue_user_tokens.issue_tokens_for_user(
  425. db, db.query(SysUser).filter_by(id="u1").one()
  426. )
  427. )
  428. assert issued["userInfo"]["role"] == "admin"
  429. assert issued["userInfo"]["roleNames"] == ["市场专员"]
  430. def test_public_auth_entrypoints_pass_the_users_tenant_to_role_queries(
  431. monkeypatch,
  432. ):
  433. from app.api import public
  434. from app.schemas.auth import ProfileUpdateRequest
  435. db = _session()
  436. _add_auth_user(db)
  437. db.commit()
  438. user = db.query(SysUser).filter_by(id="u1").one()
  439. calls = {"active": [], "role": [], "names": [], "menus": []}
  440. def has_active_roles(db_arg, user_id, tenant_no=None):
  441. calls["active"].append((user_id, tenant_no))
  442. return True
  443. def resolve_role(db_arg, user_id, tenant_no=None):
  444. calls["role"].append((user_id, tenant_no))
  445. return "admin"
  446. def role_names(db_arg, user_id, tenant_no=None):
  447. calls["names"].append((user_id, tenant_no))
  448. return ["市场专员"]
  449. def menu_codes(db_arg, user_id, tenant_no=None):
  450. calls["menus"].append((user_id, tenant_no))
  451. return {"mini:market:specialist:presign"}
  452. async def no_op(*args, **kwargs):
  453. return None
  454. async def valid(*args, **kwargs):
  455. return True
  456. current_user = {
  457. "user_id": "u1",
  458. "tenant_no": "tenant-b",
  459. "role": "admin",
  460. }
  461. monkeypatch.setattr(public.login_crypto, "ensure_initialized", lambda: None)
  462. monkeypatch.setattr(
  463. public.login_crypto,
  464. "extract_login_credentials",
  465. lambda body: ("u1", "password", None),
  466. )
  467. monkeypatch.setattr(public.user_crud, "get_by_user_no", lambda db_arg, no: user)
  468. monkeypatch.setattr(public, "verify_password", lambda raw, hashed: True)
  469. monkeypatch.setattr(public, "user_has_active_roles", has_active_roles)
  470. monkeypatch.setattr(public, "resolve_web_role_for_user", resolve_role)
  471. monkeypatch.setattr(public, "get_user_role_names", role_names)
  472. monkeypatch.setattr(public, "iter_user_menu_perm_codes", menu_codes)
  473. monkeypatch.setattr(public, "create_access_token", lambda **kwargs: "access")
  474. monkeypatch.setattr(public, "create_refresh_token", lambda **kwargs: "refresh")
  475. monkeypatch.setattr(public, "store_session", no_op)
  476. monkeypatch.setattr(public, "store_refresh_token", no_op)
  477. monkeypatch.setattr(public, "revoke_refresh_token", no_op)
  478. monkeypatch.setattr(public, "extend_session", no_op)
  479. monkeypatch.setattr(public, "is_session_valid", valid)
  480. monkeypatch.setattr(public, "is_refresh_token_valid", valid)
  481. monkeypatch.setattr(
  482. public,
  483. "decode_token",
  484. lambda token: {
  485. "type": "refresh",
  486. "sid": "session-u1",
  487. "jti": "refresh-u1",
  488. "sub": "u1",
  489. },
  490. )
  491. monkeypatch.setattr(public, "update_user", lambda *args, **kwargs: None)
  492. async def resolve_current(request, db_arg):
  493. return current_user
  494. async def current_by_token(token, db):
  495. return current_user
  496. monkeypatch.setattr(public, "_resolve_current_user_from_request", resolve_current)
  497. monkeypatch.setattr(public, "get_current_user_by_token", current_by_token)
  498. class RequestStub:
  499. headers = {"Authorization": "Bearer token"}
  500. asyncio.run(public.web_login(request=None, body={}, db=db))
  501. asyncio.run(public.refresh_token({"refreshToken": "refresh"}, db=db))
  502. asyncio.run(
  503. public.update_current_user_profile(
  504. request=RequestStub(),
  505. body=ProfileUpdateRequest(),
  506. db=db,
  507. )
  508. )
  509. asyncio.run(public.get_web_auth_menu_codes(request=RequestStub(), db=db))
  510. assert calls == {
  511. "active": [("u1", "tenant-b")],
  512. "role": [("u1", "tenant-b")] * 3,
  513. "names": [("u1", "tenant-b")] * 2,
  514. "menus": [("u1", "tenant-b")],
  515. }
  516. def test_org_labels_are_tenant_scoped_in_auth_profile_and_token_payloads(
  517. monkeypatch,
  518. ):
  519. from app.api import public
  520. from app.core.config import settings
  521. from app.crud.user import list_post_ids_for_user
  522. from app.schemas.auth import ProfileUpdateRequest
  523. from app.services import issue_user_tokens
  524. db = _session()
  525. _add_auth_user(db)
  526. _add_role_menu(
  527. db,
  528. tenant_no="tenant-b",
  529. role_id="tenant-b-role",
  530. menu_id="tenant-b-menu",
  531. perm_code="mini:market:specialist:presign",
  532. role_name="市场专员",
  533. data_scope="4",
  534. )
  535. user = db.query(SysUser).filter_by(id="u1").one()
  536. user.dept_id = "default-dept"
  537. db.add_all(
  538. [
  539. SysUserRole(
  540. id="tenant-b-role-link",
  541. user_id="u1",
  542. role_id="tenant-b-role",
  543. tenant_no="tenant-b",
  544. create_dept_id="",
  545. del_flag=0,
  546. ),
  547. SysDept(
  548. id="default-dept",
  549. dept_name="默认租户部门",
  550. tenant_no=settings.DEFAULT_TENANT_NO,
  551. create_dept_id="",
  552. del_flag=0,
  553. ),
  554. SysDept(
  555. id="tenant-b-dept",
  556. dept_name="租户B部门",
  557. tenant_no="tenant-b",
  558. create_dept_id="",
  559. del_flag=0,
  560. ),
  561. SysDept(
  562. id="tenant-b-deleted-dept",
  563. dept_name="租户B已删除部门",
  564. tenant_no="tenant-b",
  565. create_dept_id="",
  566. del_flag=1,
  567. ),
  568. SysPost(
  569. id="default-post",
  570. post_name="默认租户职位",
  571. post_code="default-post",
  572. status="1",
  573. tenant_no=settings.DEFAULT_TENANT_NO,
  574. create_dept_id="",
  575. del_flag=0,
  576. ),
  577. SysPost(
  578. id="tenant-b-post",
  579. post_name="租户B有效职位",
  580. post_code="tenant-b-post",
  581. status="1",
  582. tenant_no="tenant-b",
  583. create_dept_id="",
  584. del_flag=0,
  585. ),
  586. SysPost(
  587. id="tenant-b-disabled-post",
  588. post_name="租户B停用职位",
  589. post_code="tenant-b-disabled-post",
  590. status="0",
  591. tenant_no="tenant-b",
  592. create_dept_id="",
  593. del_flag=0,
  594. ),
  595. SysPost(
  596. id="tenant-b-deleted-post",
  597. post_name="租户B已删除职位",
  598. post_code="tenant-b-deleted-post",
  599. status="1",
  600. tenant_no="tenant-b",
  601. create_dept_id="",
  602. del_flag=1,
  603. ),
  604. SysPost(
  605. id="tenant-b-unbound-post",
  606. post_name="租户B已解绑职位",
  607. post_code="tenant-b-unbound-post",
  608. status="1",
  609. tenant_no="tenant-b",
  610. create_dept_id="",
  611. del_flag=0,
  612. ),
  613. SysUserPost(
  614. id="default-user-post",
  615. user_id="u1",
  616. post_id="default-post",
  617. tenant_no=settings.DEFAULT_TENANT_NO,
  618. create_dept_id="",
  619. del_flag=0,
  620. ),
  621. SysUserPost(
  622. id="tenant-b-user-post",
  623. user_id="u1",
  624. post_id="tenant-b-post",
  625. tenant_no="tenant-b",
  626. create_dept_id="",
  627. del_flag=0,
  628. ),
  629. SysUserPost(
  630. id="tenant-b-disabled-user-post",
  631. user_id="u1",
  632. post_id="tenant-b-disabled-post",
  633. tenant_no="tenant-b",
  634. create_dept_id="",
  635. del_flag=0,
  636. ),
  637. SysUserPost(
  638. id="tenant-b-deleted-user-post",
  639. user_id="u1",
  640. post_id="tenant-b-deleted-post",
  641. tenant_no="tenant-b",
  642. create_dept_id="",
  643. del_flag=0,
  644. ),
  645. SysUserPost(
  646. id="tenant-b-unbound-user-post",
  647. user_id="u1",
  648. post_id="tenant-b-unbound-post",
  649. tenant_no="tenant-b",
  650. create_dept_id="",
  651. del_flag=1,
  652. ),
  653. ]
  654. )
  655. db.commit()
  656. dept_name, post_names = issue_user_tokens.resolve_user_org_labels(db, user)
  657. assert dept_name is None
  658. assert post_names == ["租户B有效职位"]
  659. assert set(list_post_ids_for_user(db, "u1")) == {
  660. "default-post",
  661. "tenant-b-post",
  662. "tenant-b-disabled-post",
  663. "tenant-b-deleted-post",
  664. }
  665. assert set(list_post_ids_for_user(db, "u1", tenant_no="tenant-b")) == {
  666. "tenant-b-post",
  667. "tenant-b-disabled-post",
  668. "tenant-b-deleted-post",
  669. }
  670. user.dept_id = "tenant-b-deleted-dept"
  671. db.commit()
  672. dept_name, post_names = issue_user_tokens.resolve_user_org_labels(db, user)
  673. assert dept_name is None
  674. assert post_names == ["租户B有效职位"]
  675. user.dept_id = "tenant-b-dept"
  676. db.commit()
  677. authenticated = _authenticate(db, monkeypatch)
  678. assert authenticated["dept_name"] == "租户B部门"
  679. assert authenticated["post_names"] == ["租户B有效职位"]
  680. async def no_op(*args, **kwargs):
  681. return None
  682. monkeypatch.setattr(issue_user_tokens, "store_session", no_op)
  683. monkeypatch.setattr(issue_user_tokens, "store_refresh_token", no_op)
  684. issued = asyncio.run(issue_user_tokens.issue_tokens_for_user(db, user))
  685. assert issued["userInfo"]["deptName"] == "租户B部门"
  686. assert issued["userInfo"]["postNames"] == ["租户B有效职位"]
  687. current_user = {"user_id": "u1", "tenant_no": "tenant-b"}
  688. async def resolve_current(request, db_arg):
  689. return current_user
  690. monkeypatch.setattr(public, "_resolve_current_user_from_request", resolve_current)
  691. monkeypatch.setattr(public, "update_user", lambda *args, **kwargs: None)
  692. profiled = asyncio.run(
  693. public.update_current_user_profile(
  694. request=None,
  695. body=ProfileUpdateRequest(),
  696. db=db,
  697. )
  698. )
  699. assert profiled["data"]["deptName"] == "租户B部门"
  700. assert profiled["data"]["postNames"] == ["租户B有效职位"]