test_mock_db.py 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  1. """模拟 MySQL(DB_SYNC 对端)守卫:DDL 生成、类型推断、库名硬阻断、配置清单。
  2. 需要真实 MySQL 的用例在未设置 AIDOP_SIM_MYSQL_PASSWORD 时跳过,并明确标注“未实测”,
  3. 不得把跳过当作通过。
  4. """
  5. from __future__ import annotations
  6. import os
  7. import pytest
  8. from catalog import sample_adapters as sa
  9. from providers import mock_db
  10. RUN_ID = "20260930120000-abc123"
  11. def test_blocks_aidop_databases():
  12. for bad in ("aidopdev", "aidop_uat", "aidop_prod", "aidop", "admin", "mdp_std"):
  13. with pytest.raises(mock_db.BlockedDatabaseError):
  14. mock_db.assert_sim_database(bad)
  15. def test_allows_simulator_schema():
  16. mock_db.assert_sim_database(mock_db.SIM_DB_DEFAULT)
  17. mock_db.assert_sim_database("aidop_integration_sim_uat")
  18. def test_rejects_empty_database_name():
  19. with pytest.raises(mock_db.BlockedDatabaseError):
  20. mock_db.assert_sim_database("")
  21. def test_conn_params_never_carry_aidop_db(monkeypatch):
  22. monkeypatch.setenv("AIDOP_SIM_MYSQL_DATABASE", "aidopdev")
  23. with pytest.raises(mock_db.BlockedDatabaseError):
  24. mock_db.mysql_conn_params()
  25. def test_type_inference():
  26. assert mock_db.infer_mysql_type([1, 2, None]) == "BIGINT"
  27. assert mock_db.infer_mysql_type([1.5]) == "DECIMAL(18,6)"
  28. assert mock_db.infer_mysql_type([True]) == "TINYINT(1)"
  29. assert mock_db.infer_mysql_type(["abc"]).startswith("VARCHAR(")
  30. assert mock_db.infer_mysql_type([None]) == "VARCHAR(191)"
  31. def test_ddl_has_business_unique_key_and_increment_index():
  32. rows = sa.adapt_db_rows(sa.find_object("ITEM"), RUN_ID)
  33. ddl = mock_db.generate_ddl("sim_item", rows, ["bizKey"], "sourceUpdatedAt")
  34. assert ddl.startswith("CREATE TABLE IF NOT EXISTS `sim_item`")
  35. assert "UNIQUE KEY `uk_sim_item` (`bizKey`)" in ddl
  36. assert "KEY `ix_sim_item_incr` (`sourceUpdatedAt`)" in ddl
  37. assert "ENGINE=InnoDB" in ddl
  38. def test_ddl_rejects_empty_rows():
  39. with pytest.raises(ValueError):
  40. mock_db.generate_ddl("sim_x", [], ["bizKey"])
  41. def test_upsert_sql_is_idempotent():
  42. sql = mock_db._insert_sql("sim_item", ["bizKey", "sourceUpdatedAt"])
  43. assert "ON DUPLICATE KEY UPDATE" in sql
  44. assert sql.count("%s") == 2
  45. def test_aidop_config_uses_dedicated_test_sources():
  46. cfg = mock_db.generate_aidop_config(sa.find_object("ITEM"))
  47. codes = {s["source_code"] for s in cfg["sources"]}
  48. assert codes == {"SIM_MODE3_DB", "SIM_MODE3_API", "SIM_MODE3_INBOUND"}
  49. assert cfg["systemCode"] == "SIM_MODE3"
  50. db_entity = next(e for e in cfg["entities"] if e["source_code"] == "SIM_MODE3_DB")
  51. assert db_entity["source_table_name"] == "sim_item"
  52. assert db_entity["incr_column"] == "sourceUpdatedAt"
  53. def test_aidop_config_omits_inbound_when_unsupported():
  54. cfg = mock_db.generate_aidop_config(sa.find_object("SALES_ORDER_HEAD"))
  55. assert "SIM_MODE3_INBOUND" not in {s["source_code"] for s in cfg["sources"]}
  56. requires_mysql = pytest.mark.skipif(
  57. not os.environ.get("AIDOP_SIM_MYSQL_PASSWORD"),
  58. reason="未设置 AIDOP_SIM_MYSQL_PASSWORD:真实 MySQL 用例未执行(不代表通过)")
  59. @requires_mysql
  60. def test_live_schema_load_and_increment():
  61. obj = sa.find_object("ITEM")
  62. spec = obj["dbSync"]
  63. rows = sa.adapt_db_rows(obj, RUN_ID)
  64. mock_db.init_schema({spec["table"]: {"rows": rows, "businessKey": spec["businessKey"],
  65. "incrementColumn": spec["incrementColumn"]}})
  66. mock_db.reset_table(spec["table"])
  67. assert mock_db.upsert_rows(spec["table"], rows) == len(rows)
  68. # 重复装载不得产生重复业务行
  69. mock_db.upsert_rows(spec["table"], rows)
  70. stats = mock_db.table_stats(spec["table"], spec["incrementColumn"])
  71. assert stats["rowCount"] == len(rows)
  72. assert stats["maxIncrement"]
  73. # 更新并推进增量列
  74. affected = mock_db.update_rows(spec["table"], {"itemName": "更新后"},
  75. {"bizKey": rows[0]["bizKey"]}, spec["incrementColumn"],
  76. "2026-09-30T23:59:59")
  77. assert affected == 1
  78. assert mock_db.table_stats(spec["table"], spec["incrementColumn"])["maxIncrement"] >= "2026-09-30"