test_market_field_scope.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709
  1. import re
  2. from pathlib import Path
  3. import pytest
  4. from fastapi import HTTPException
  5. from sqlalchemy import create_engine
  6. from sqlalchemy.orm import sessionmaker
  7. from app.models.base import Base
  8. from app.models.models import (
  9. BizCollectionProtocol,
  10. BizCollectionRegionStaff,
  11. BizPresignOrder,
  12. BizProtocolCollectionInfo,
  13. SysPost,
  14. SysPostRole,
  15. SysRole,
  16. SysRoleDataScopeDetail,
  17. SysUserPost,
  18. SysUserRole,
  19. )
  20. from app.models.research_donation import (
  21. BizResearchDonation,
  22. BizResearchDonationCollection,
  23. )
  24. def _session():
  25. engine = create_engine("sqlite:///:memory:")
  26. Base.metadata.create_all(
  27. engine,
  28. tables=[
  29. BizCollectionRegionStaff.__table__,
  30. BizPresignOrder.__table__,
  31. BizCollectionProtocol.__table__,
  32. BizProtocolCollectionInfo.__table__,
  33. BizResearchDonation.__table__,
  34. BizResearchDonationCollection.__table__,
  35. SysRole.__table__,
  36. SysRoleDataScopeDetail.__table__,
  37. SysUserRole.__table__,
  38. SysPost.__table__,
  39. SysPostRole.__table__,
  40. SysUserPost.__table__,
  41. ],
  42. )
  43. return sessionmaker(bind=engine)()
  44. def _presign(
  45. order_id,
  46. *,
  47. tenant_no="tenant-a",
  48. region_id="region-a",
  49. sales_user_id="sales-a",
  50. manager_user_id="manager-a",
  51. create_by="creator-a",
  52. del_flag=0,
  53. ):
  54. return BizPresignOrder(
  55. id=order_id,
  56. tenant_no=tenant_no,
  57. region_id=region_id,
  58. sales_user_id=sales_user_id,
  59. region_manager_user_id=manager_user_id,
  60. create_by=create_by,
  61. create_dept_id="dept-a",
  62. del_flag=del_flag,
  63. )
  64. def _cell_protocol(
  65. protocol_id,
  66. *,
  67. tenant_no="tenant-a",
  68. collection_tenant_no=None,
  69. region_id="region-a",
  70. sales_user_id="sales-a",
  71. manager_user_id="manager-a",
  72. parent_del_flag=0,
  73. collection_del_flag=0,
  74. ):
  75. collection_tenant_no = collection_tenant_no or tenant_no
  76. return [
  77. BizCollectionProtocol(
  78. id=protocol_id,
  79. protocol_no=f"CP-{protocol_id}",
  80. tenant_no=tenant_no,
  81. create_by=sales_user_id,
  82. create_dept_id="dept-a",
  83. del_flag=parent_del_flag,
  84. ),
  85. BizProtocolCollectionInfo(
  86. id=f"ci-{protocol_id}",
  87. collection_protocol_id=protocol_id,
  88. tenant_no=collection_tenant_no,
  89. region_id=region_id,
  90. sales_user_id=sales_user_id,
  91. region_manager_user_id=manager_user_id,
  92. create_dept_id="dept-a",
  93. del_flag=collection_del_flag,
  94. ),
  95. ]
  96. def _donation(
  97. donation_id,
  98. *,
  99. tenant_no="tenant-a",
  100. collection_tenant_no=None,
  101. region_id="region-a",
  102. sales_user_id="sales-a",
  103. manager_user_id="manager-a",
  104. parent_del_flag=0,
  105. collection_del_flag=0,
  106. ):
  107. collection_tenant_no = collection_tenant_no or tenant_no
  108. return [
  109. BizResearchDonation(
  110. id=donation_id,
  111. donation_no=f"RD-{donation_id}",
  112. tenant_no=tenant_no,
  113. create_by=sales_user_id,
  114. create_dept_id="dept-a",
  115. del_flag=parent_del_flag,
  116. ),
  117. BizResearchDonationCollection(
  118. id=f"dc-{donation_id}",
  119. research_donation_id=donation_id,
  120. tenant_no=collection_tenant_no,
  121. region_id=region_id,
  122. sales_user_id=sales_user_id,
  123. region_manager_user_id=manager_user_id,
  124. create_dept_id="dept-a",
  125. del_flag=collection_del_flag,
  126. ),
  127. ]
  128. def _ids(rows):
  129. return {row.id for row in rows}
  130. def _user_info(user_id="u1", role="super_admin"):
  131. return {"user_id": user_id, "role": role, "dept_id": "dept-a"}
  132. def test_specialist_matches_sales_user_only():
  133. from app.services.market_field_scope import (
  134. MarketFieldContext,
  135. apply_presign_scope,
  136. )
  137. db = _session()
  138. db.add_all(
  139. [
  140. _presign("own", sales_user_id="u1", manager_user_id="other"),
  141. _presign("managed", sales_user_id="other", manager_user_id="u1"),
  142. ]
  143. )
  144. db.commit()
  145. ctx = MarketFieldContext("tenant-a", "u1", "specialist")
  146. rows = apply_presign_scope(
  147. db.query(BizPresignOrder), db, ctx, user_info=_user_info()
  148. ).all()
  149. assert _ids(rows) == {"own"}
  150. def test_manager_matches_leader_region_or_document_manager():
  151. from app.services.market_field_scope import (
  152. MarketFieldContext,
  153. apply_presign_scope,
  154. )
  155. db = _session()
  156. db.add(
  157. BizCollectionRegionStaff(
  158. id="leader",
  159. region_id="region-led",
  160. user_id="u1",
  161. is_leader=1,
  162. tenant_no="tenant-a",
  163. create_dept_id="dept-a",
  164. del_flag=0,
  165. )
  166. )
  167. db.add_all(
  168. [
  169. _presign("by-region", region_id="region-led", manager_user_id="other"),
  170. _presign("by-manager", region_id="region-other", manager_user_id="u1"),
  171. _presign("outside", region_id="region-other", manager_user_id="other"),
  172. ]
  173. )
  174. db.commit()
  175. ctx = MarketFieldContext("tenant-a", "u1", "manager")
  176. rows = apply_presign_scope(
  177. db.query(BizPresignOrder), db, ctx, user_info=_user_info()
  178. ).all()
  179. assert _ids(rows) == {"by-region", "by-manager"}
  180. def test_manager_ignores_non_leader_staff_rows():
  181. from app.services.market_field_scope import (
  182. MarketFieldContext,
  183. apply_presign_scope,
  184. )
  185. db = _session()
  186. db.add(
  187. BizCollectionRegionStaff(
  188. id="staff",
  189. region_id="region-staff",
  190. user_id="u1",
  191. is_leader=0,
  192. tenant_no="tenant-a",
  193. create_dept_id="dept-a",
  194. del_flag=0,
  195. )
  196. )
  197. db.add(_presign("staff-region", region_id="region-staff", manager_user_id="other"))
  198. db.commit()
  199. ctx = MarketFieldContext("tenant-a", "u1", "manager")
  200. assert (
  201. apply_presign_scope(
  202. db.query(BizPresignOrder), db, ctx, user_info=_user_info()
  203. ).all()
  204. == []
  205. )
  206. def test_soft_deleted_or_cross_tenant_region_never_matches():
  207. from app.services.market_field_scope import (
  208. MarketFieldContext,
  209. apply_presign_scope,
  210. )
  211. db = _session()
  212. db.add_all(
  213. [
  214. BizCollectionRegionStaff(
  215. id="deleted-leader",
  216. region_id="region-deleted",
  217. user_id="u1",
  218. is_leader=1,
  219. tenant_no="tenant-a",
  220. create_dept_id="dept-a",
  221. del_flag=1,
  222. ),
  223. BizCollectionRegionStaff(
  224. id="other-tenant-leader",
  225. region_id="region-cross",
  226. user_id="u1",
  227. is_leader=1,
  228. tenant_no="tenant-b",
  229. create_dept_id="dept-a",
  230. del_flag=0,
  231. ),
  232. _presign("deleted-region", region_id="region-deleted", manager_user_id="other"),
  233. _presign("cross-region", region_id="region-cross", manager_user_id="other"),
  234. _presign("soft-deleted-doc", manager_user_id="u1", del_flag=1),
  235. _presign("cross-tenant-doc", tenant_no="tenant-b", manager_user_id="u1"),
  236. ]
  237. )
  238. db.commit()
  239. ctx = MarketFieldContext("tenant-a", "u1", "manager")
  240. assert (
  241. apply_presign_scope(
  242. db.query(BizPresignOrder), db, ctx, user_info=_user_info()
  243. ).all()
  244. == []
  245. )
  246. def test_dual_role_contexts_do_not_union_specialist_and_manager_scope():
  247. from app.services.market_field_scope import (
  248. MarketFieldContext,
  249. apply_presign_scope,
  250. )
  251. db = _session()
  252. db.add_all(
  253. [
  254. _presign("specialist-only", sales_user_id="u1", manager_user_id="other"),
  255. _presign("manager-only", sales_user_id="other", manager_user_id="u1"),
  256. ]
  257. )
  258. db.commit()
  259. specialist = MarketFieldContext("tenant-a", "u1", "specialist")
  260. manager = MarketFieldContext("tenant-a", "u1", "manager")
  261. assert _ids(apply_presign_scope(
  262. db.query(BizPresignOrder), db, specialist, user_info=_user_info()
  263. ).all()) == {
  264. "specialist-only"
  265. }
  266. assert _ids(apply_presign_scope(
  267. db.query(BizPresignOrder), db, manager, user_info=_user_info()
  268. ).all()) == {
  269. "manager-only"
  270. }
  271. def test_apply_scope_requires_general_data_scope_context():
  272. from app.services.market_field_scope import (
  273. MarketFieldContext,
  274. apply_presign_scope,
  275. )
  276. db = _session()
  277. ctx = MarketFieldContext("tenant-a", "u1", "specialist")
  278. with pytest.raises(TypeError):
  279. apply_presign_scope(db.query(BizPresignOrder), db, ctx)
  280. @pytest.mark.parametrize(
  281. ("apply_name", "model"),
  282. [
  283. ("apply_presign_scope", BizPresignOrder),
  284. ("apply_cell_protocol_scope", BizCollectionProtocol),
  285. ("apply_donation_scope", BizResearchDonation),
  286. ],
  287. )
  288. @pytest.mark.parametrize(
  289. "user_info",
  290. [{}, {"user_id": ""}, {"user_id": "other", "role": "super_admin"}],
  291. )
  292. def test_apply_scopes_reject_missing_or_mismatched_user_identity(
  293. apply_name, model, user_info
  294. ):
  295. from app.services import market_field_scope
  296. db = _session()
  297. ctx = market_field_scope.MarketFieldContext("tenant-a", "u1", "specialist")
  298. apply_scope = getattr(market_field_scope, apply_name)
  299. with pytest.raises(PermissionError):
  300. apply_scope(db.query(model), db, ctx, user_info=user_info)
  301. @pytest.mark.parametrize(
  302. "assert_name",
  303. [
  304. "assert_presign_access",
  305. "assert_cell_protocol_access",
  306. "assert_donation_access",
  307. ],
  308. )
  309. @pytest.mark.parametrize(
  310. "user_info",
  311. [{}, {"user_id": ""}, {"user_id": "other", "role": "super_admin"}],
  312. )
  313. def test_assert_access_rejects_missing_or_mismatched_user_identity(
  314. assert_name, user_info
  315. ):
  316. from app.services import market_field_scope
  317. db = _session()
  318. ctx = market_field_scope.MarketFieldContext("tenant-a", "u1", "specialist")
  319. assert_access = getattr(market_field_scope, assert_name)
  320. with pytest.raises(PermissionError):
  321. assert_access(db, "any-id", ctx, user_info=user_info)
  322. def test_protocol_and_donation_queries_use_their_collection_info_scope():
  323. from app.services.market_field_scope import (
  324. MarketFieldContext,
  325. apply_cell_protocol_scope,
  326. apply_donation_scope,
  327. )
  328. db = _session()
  329. db.add_all(
  330. _cell_protocol("cell-own", sales_user_id="u1")
  331. + _cell_protocol("cell-other", sales_user_id="other")
  332. + _cell_protocol("cell-deleted-info", sales_user_id="u1", collection_del_flag=1)
  333. + _donation("donation-own", sales_user_id="u1")
  334. + _donation("donation-other", sales_user_id="other")
  335. + _donation("donation-deleted-info", sales_user_id="u1", collection_del_flag=1)
  336. )
  337. db.commit()
  338. ctx = MarketFieldContext("tenant-a", "u1", "specialist")
  339. assert _ids(
  340. apply_cell_protocol_scope(
  341. db.query(BizCollectionProtocol), db, ctx, user_info=_user_info()
  342. ).all()
  343. ) == {"cell-own"}
  344. assert _ids(
  345. apply_donation_scope(
  346. db.query(BizResearchDonation), db, ctx, user_info=_user_info()
  347. ).all()
  348. ) == {"donation-own"}
  349. def test_cell_and_donation_manager_scope_uses_leader_region_or_document_manager():
  350. from app.services.market_field_scope import (
  351. MarketFieldContext,
  352. apply_cell_protocol_scope,
  353. apply_donation_scope,
  354. )
  355. db = _session()
  356. db.add_all(
  357. [
  358. BizCollectionRegionStaff(
  359. id="leader",
  360. region_id="region-led",
  361. user_id="u1",
  362. is_leader=1,
  363. tenant_no="tenant-a",
  364. create_dept_id="dept-a",
  365. del_flag=0,
  366. ),
  367. BizCollectionRegionStaff(
  368. id="non-leader",
  369. region_id="region-staff",
  370. user_id="u1",
  371. is_leader=0,
  372. tenant_no="tenant-a",
  373. create_dept_id="dept-a",
  374. del_flag=0,
  375. ),
  376. ]
  377. + _cell_protocol("cell-region", region_id="region-led", manager_user_id="other")
  378. + _cell_protocol("cell-manager", region_id="other", manager_user_id="u1")
  379. + _cell_protocol("cell-non-leader", region_id="region-staff", manager_user_id="other")
  380. + _cell_protocol(
  381. "cell-cross-info",
  382. collection_tenant_no="tenant-b",
  383. region_id="region-led",
  384. manager_user_id="u1",
  385. )
  386. + _cell_protocol("cell-deleted-parent", manager_user_id="u1", parent_del_flag=1)
  387. + _cell_protocol("cell-cross-parent", tenant_no="tenant-b", manager_user_id="u1")
  388. + _donation("donation-region", region_id="region-led", manager_user_id="other")
  389. + _donation("donation-manager", region_id="other", manager_user_id="u1")
  390. + _donation("donation-non-leader", region_id="region-staff", manager_user_id="other")
  391. + _donation(
  392. "donation-cross-info",
  393. collection_tenant_no="tenant-b",
  394. region_id="region-led",
  395. manager_user_id="u1",
  396. )
  397. + _donation("donation-deleted-parent", manager_user_id="u1", parent_del_flag=1)
  398. + _donation("donation-cross-parent", tenant_no="tenant-b", manager_user_id="u1")
  399. )
  400. db.commit()
  401. ctx = MarketFieldContext("tenant-a", "u1", "manager")
  402. assert _ids(
  403. apply_cell_protocol_scope(
  404. db.query(BizCollectionProtocol), db, ctx, user_info=_user_info()
  405. ).all()
  406. ) == {"cell-region", "cell-manager"}
  407. assert _ids(
  408. apply_donation_scope(
  409. db.query(BizResearchDonation), db, ctx, user_info=_user_info()
  410. ).all()
  411. ) == {"donation-region", "donation-manager"}
  412. def test_assert_access_uses_the_same_scope_for_all_document_types():
  413. from app.services.market_field_scope import (
  414. MarketFieldContext,
  415. assert_cell_protocol_access,
  416. assert_donation_access,
  417. assert_presign_access,
  418. )
  419. db = _session()
  420. db.add_all(
  421. [
  422. _presign("presign-own", sales_user_id="u1"),
  423. _presign("presign-outside", sales_user_id="other"),
  424. _presign("presign-cross", tenant_no="tenant-b", sales_user_id="u1"),
  425. _presign("presign-deleted", sales_user_id="u1", del_flag=1),
  426. ]
  427. )
  428. db.add_all(_cell_protocol("cell-own", sales_user_id="u1"))
  429. db.add_all(_cell_protocol("cell-outside", sales_user_id="other"))
  430. db.add_all(_cell_protocol("cell-cross", tenant_no="tenant-b", sales_user_id="u1"))
  431. db.add_all(_cell_protocol("cell-deleted", sales_user_id="u1", parent_del_flag=1))
  432. db.add_all(_donation("donation-own", sales_user_id="u1"))
  433. db.add_all(_donation("donation-outside", sales_user_id="other"))
  434. db.add_all(_donation("donation-cross", tenant_no="tenant-b", sales_user_id="u1"))
  435. db.add_all(_donation("donation-deleted", sales_user_id="u1", parent_del_flag=1))
  436. db.commit()
  437. ctx = MarketFieldContext("tenant-a", "u1", "specialist")
  438. assert assert_presign_access(
  439. db, "presign-own", ctx, user_info=_user_info()
  440. ).id == "presign-own"
  441. assert assert_cell_protocol_access(
  442. db, "cell-own", ctx, user_info=_user_info()
  443. ).id == "cell-own"
  444. assert assert_donation_access(
  445. db, "donation-own", ctx, user_info=_user_info()
  446. ).id == "donation-own"
  447. rejected = {
  448. assert_presign_access: [
  449. "presign-outside",
  450. "presign-cross",
  451. "presign-deleted",
  452. ],
  453. assert_cell_protocol_access: [
  454. "cell-outside",
  455. "cell-cross",
  456. "cell-deleted",
  457. ],
  458. assert_donation_access: [
  459. "donation-outside",
  460. "donation-cross",
  461. "donation-deleted",
  462. ],
  463. }
  464. for checker, record_ids in rejected.items():
  465. for record_id in record_ids:
  466. with pytest.raises(HTTPException) as exc_info:
  467. checker(db, record_id, ctx, user_info=_user_info())
  468. assert exc_info.value.status_code == 404
  469. def test_general_data_scope_and_field_scope_are_intersected():
  470. from app.services.market_field_scope import (
  471. MarketFieldContext,
  472. apply_presign_scope,
  473. )
  474. db = _session()
  475. db.add_all(
  476. [
  477. SysRole(
  478. id="self-role",
  479. role_name="仅本人",
  480. data_scope="4",
  481. status="1",
  482. tenant_no="tenant-a",
  483. create_dept_id="dept-a",
  484. del_flag=0,
  485. ),
  486. SysUserRole(
  487. id="user-role",
  488. user_id="u1",
  489. role_id="self-role",
  490. tenant_no="tenant-a",
  491. create_dept_id="dept-a",
  492. del_flag=0,
  493. ),
  494. _presign("both", sales_user_id="u1", create_by="u1"),
  495. _presign("field-only", sales_user_id="u1", create_by="other"),
  496. _presign("data-only", sales_user_id="other", create_by="u1"),
  497. ]
  498. )
  499. db.commit()
  500. ctx = MarketFieldContext("tenant-a", "u1", "specialist")
  501. rows = apply_presign_scope(
  502. db.query(BizPresignOrder),
  503. db,
  504. ctx,
  505. user_info={"user_id": "u1", "role": "admin", "dept_id": "dept-a"},
  506. ).all()
  507. assert _ids(rows) == {"both"}
  508. def test_multi_role_union_is_still_intersected_with_field_scope():
  509. from app.services.market_field_scope import (
  510. MarketFieldContext,
  511. apply_presign_scope,
  512. )
  513. db = _session()
  514. db.add_all(
  515. [
  516. SysRole(
  517. id="self-role",
  518. role_name="仅本人",
  519. data_scope="4",
  520. status="1",
  521. tenant_no="tenant-a",
  522. create_dept_id="dept-a",
  523. del_flag=0,
  524. ),
  525. SysRole(
  526. id="all-role",
  527. role_name="全部数据",
  528. data_scope="1",
  529. status="1",
  530. tenant_no="tenant-a",
  531. create_dept_id="dept-a",
  532. del_flag=0,
  533. ),
  534. SysUserRole(
  535. id="self-user-role",
  536. user_id="u1",
  537. role_id="self-role",
  538. tenant_no="tenant-a",
  539. create_dept_id="dept-a",
  540. del_flag=0,
  541. ),
  542. SysUserRole(
  543. id="all-user-role",
  544. user_id="u1",
  545. role_id="all-role",
  546. tenant_no="tenant-a",
  547. create_dept_id="dept-a",
  548. del_flag=0,
  549. ),
  550. _presign("both", sales_user_id="u1", create_by="u1"),
  551. _presign("field-only", sales_user_id="u1", create_by="other"),
  552. _presign("data-only", sales_user_id="other", create_by="u1"),
  553. ]
  554. )
  555. db.commit()
  556. ctx = MarketFieldContext("tenant-a", "u1", "specialist")
  557. rows = apply_presign_scope(
  558. db.query(BizPresignOrder),
  559. db,
  560. ctx,
  561. user_info=_user_info(role="admin"),
  562. ).all()
  563. assert _ids(rows) == {"both", "field-only"}
  564. def test_market_field_scope_index_migration_is_idempotent_and_complete():
  565. migration = (
  566. Path(__file__).resolve().parents[2]
  567. / "docs/sql/migrations/2026-10-10-market-field-scope-indexes.sql"
  568. )
  569. sql = migration.read_text(encoding="utf-8")
  570. compact = re.sub(r"\s+", " ", re.sub(r"--[^\n]*", "", sql)).lower()
  571. expected = {
  572. "idx_crs_market_leader_scope": (
  573. "biz_collection_region_staff",
  574. "tenant_no,user_id,is_leader,del_flag,region_id",
  575. ),
  576. "idx_po_market_sales_scope": (
  577. "biz_presign_order",
  578. "tenant_no,del_flag,sales_user_id",
  579. ),
  580. "idx_po_market_region_scope": (
  581. "biz_presign_order",
  582. "tenant_no,del_flag,region_id",
  583. ),
  584. "idx_po_market_manager_scope": (
  585. "biz_presign_order",
  586. "tenant_no,del_flag,region_manager_user_id",
  587. ),
  588. "idx_pci_market_parent_scope": (
  589. "biz_protocol_collection_info",
  590. "tenant_no,del_flag,collection_protocol_id",
  591. ),
  592. "idx_pci_market_sales_scope": (
  593. "biz_protocol_collection_info",
  594. "tenant_no,del_flag,sales_user_id,collection_protocol_id",
  595. ),
  596. "idx_pci_market_region_scope": (
  597. "biz_protocol_collection_info",
  598. "tenant_no,del_flag,region_id,collection_protocol_id",
  599. ),
  600. "idx_pci_market_manager_scope": (
  601. "biz_protocol_collection_info",
  602. "tenant_no,del_flag,region_manager_user_id,collection_protocol_id",
  603. ),
  604. "idx_rdc_market_parent_scope": (
  605. "biz_research_donation_collection",
  606. "tenant_no,del_flag,research_donation_id",
  607. ),
  608. "idx_rdc_market_sales_scope": (
  609. "biz_research_donation_collection",
  610. "tenant_no,del_flag,sales_user_id,research_donation_id",
  611. ),
  612. "idx_rdc_market_region_scope": (
  613. "biz_research_donation_collection",
  614. "tenant_no,del_flag,region_id,research_donation_id",
  615. ),
  616. "idx_rdc_market_manager_scope": (
  617. "biz_research_donation_collection",
  618. "tenant_no,del_flag,region_manager_user_id,research_donation_id",
  619. ),
  620. }
  621. assert compact.count("information_schema.statistics") == len(expected)
  622. assert compact.count("prepare stmt from @sql") == len(expected)
  623. assert compact.count("deallocate prepare stmt") == len(expected)
  624. for index_name, (table_name, columns) in expected.items():
  625. assert f"table_name = '{table_name}'" in compact
  626. assert f"index_name = '{index_name}'" in compact
  627. column_sql = ",".join(f"`{column}`" for column in columns.split(","))
  628. assert re.search(
  629. rf"alter table `{table_name}` add index `{index_name}` \({column_sql}\)",
  630. compact,
  631. )