178 lines
7.0 KiB
Python
178 lines
7.0 KiB
Python
"""INSERT 表主键碰撞预置行生成测试(T2)。
|
||
|
||
gixsql+SQLite 下,`IF SQLCODE = -803` 分支要求运行时 INSERT 失败。
|
||
让 INSERT 失败的唯一途径是 DB 中已存在同主键行。因此 build_db_input
|
||
必须为"要求 SQLCODE≠0 的路径"生成主键碰撞预置行(主键值取自该记录)。
|
||
"""
|
||
|
||
import sys, os
|
||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..")))
|
||
from cobol_testgen.to_sql import build_db_input
|
||
|
||
|
||
def _sql_meta_insert(table="EMP-MASTER", columns=("EMP-ID", "EMP-NAME"),
|
||
host_vars=("DBV-EMP-ID", "DBV-EMP-NAME")):
|
||
return [{
|
||
"type": "exec_sql_insert",
|
||
"table": table,
|
||
"columns": list(columns),
|
||
"host_vars": list(host_vars),
|
||
"sql_text": f"INSERT INTO {table} ({', '.join(columns)}) VALUES ({', '.join(':'+h for h in host_vars)})",
|
||
}]
|
||
|
||
|
||
def test_insert_sql_ok_path_skips_collision_row():
|
||
"""要求 SQLCODE=0(INSERT 成功)的路径 → 不生成碰撞预置行"""
|
||
branch_paths = [
|
||
([('SQLCODE', '=', '0', True)], {}), # SQL ok → skip
|
||
]
|
||
records = [{'DBV-EMP-ID': 'A0000001', 'DBV-EMP-NAME': 'NAME'}]
|
||
db_input = build_db_input(
|
||
branch_paths, [], {}, _sql_meta_insert(),
|
||
{'EMP-MASTER': [{'name': 'EMP-ID', 'db_type': 'CHAR', 'size': 8}]},
|
||
records=records,
|
||
insert_pk={'EMP-MASTER': ['EMP-ID']},
|
||
)
|
||
assert 'EMP-MASTER' not in db_input or db_input['EMP-MASTER'] == []
|
||
|
||
|
||
def test_insert_error_path_generates_pk_collision_row():
|
||
"""要求 SQLCODE<>0(INSERT 失败)的路径 → 生成主键碰撞预置行"""
|
||
branch_paths = [
|
||
([('SQLCODE', '<>', '0', True)], {}), # SQL fails → collision row
|
||
]
|
||
records = [{'DBV-EMP-ID': 'A0000001', 'DBV-EMP-NAME': 'NAME'}]
|
||
db_input = build_db_input(
|
||
branch_paths, [], {}, _sql_meta_insert(),
|
||
{'EMP-MASTER': [{'name': 'EMP-ID', 'db_type': 'CHAR', 'size': 8},
|
||
{'name': 'EMP-NAME', 'db_type': 'CHAR', 'size': 40}]},
|
||
records=records,
|
||
insert_pk={'EMP-MASTER': ['EMP-ID']},
|
||
)
|
||
rows = db_input.get('EMP-MASTER', [])
|
||
assert len(rows) == 1
|
||
assert rows[0]['EMP-ID'] == 'A0000001'
|
||
|
||
|
||
def test_insert_error_path_uses_record_host_var_value():
|
||
"""碰撞行主键取自记录中对应 host_var 的值"""
|
||
branch_paths = [
|
||
([('SQLCODE', '=', '-803', True)], {}),
|
||
]
|
||
records = [{'DBV-EMP-ID': 'Z9999999', 'DBV-EMP-NAME': 'DUPE'}]
|
||
db_input = build_db_input(
|
||
branch_paths, [], {}, _sql_meta_insert(),
|
||
{'EMP-MASTER': [{'name': 'EMP-ID', 'db_type': 'CHAR', 'size': 8}]},
|
||
records=records,
|
||
insert_pk={'EMP-MASTER': ['EMP-ID']},
|
||
)
|
||
rows = db_input.get('EMP-MASTER', [])
|
||
assert rows and rows[0]['EMP-ID'] == 'Z9999999'
|
||
|
||
|
||
def test_insert_collision_uses_input_record_pk_not_hostvar():
|
||
"""碰撞行主键用输入记录主键(R02EMP-ID),而非 expected host_var 值。
|
||
|
||
generate_records 的 rec 中 DBV-EMP-ID 可能是 DB 预置值(W前缀)或
|
||
expected 值,而运行时实际 INSERT 的是输入记录主键(R02EMP-ID)。
|
||
碰撞行必须与输入主键一致,运行时 INSERT 才会失败。
|
||
"""
|
||
branch_paths = [
|
||
([('SQLCODE', '=', '-803', True)], {}),
|
||
]
|
||
records = [{'R02EMP-ID': 'A0000001', 'DBV-EMP-ID': 'W0000001'}]
|
||
db_input = build_db_input(
|
||
branch_paths, [], {}, _sql_meta_insert(),
|
||
{'EMP-MASTER': [{'name': 'EMP-ID', 'db_type': 'CHAR', 'size': 8}]},
|
||
records=records,
|
||
insert_pk={'EMP-MASTER': ['EMP-ID']},
|
||
)
|
||
rows = db_input.get('EMP-MASTER', [])
|
||
assert rows and rows[0]['EMP-ID'] == 'A0000001'
|
||
|
||
|
||
def _sql_meta_insert_salary_results():
|
||
"""KYU06UPD 的 INSERT 语句形态(复合主键 EMP-ID + YEAR-MONTH)。"""
|
||
return [{
|
||
"type": "exec_sql_insert",
|
||
"table": "SALARY-RESULTS",
|
||
"columns": ["EMP-ID", "YEAR-MONTH", "GROSS-PAYMENT"],
|
||
"host_vars": ["DBV-EMP-ID", "DBV-YEAR-MONTH", "DBV-GROSS-PAYMENT"],
|
||
"sql_text": ("INSERT INTO SALARY-RESULTS (EMP-ID, YEAR-MONTH, GROSS-PAYMENT) "
|
||
"VALUES (:DBV-EMP-ID, :DBV-YEAR-MONTH, :DBV-GROSS-PAYMENT)"),
|
||
}]
|
||
|
||
|
||
def _declared_salary_results():
|
||
return {
|
||
"SALARY-RESULTS": [
|
||
{"name": "EMP-ID", "db_type": "CHAR", "size": 8},
|
||
{"name": "YEAR-MONTH", "db_type": "CHAR", "size": 6},
|
||
{"name": "GROSS-PAYMENT", "db_type": "DECIMAL", "size": 9},
|
||
],
|
||
}
|
||
|
||
|
||
def _kyu06_assignments():
|
||
"""KYU06UPD 的主键 MOVE 链:
|
||
DBV-EMP-ID ← R02EMP-ID(输入键)
|
||
DBV-YEAR-MONTH ← CNS-YEAR-MONTH-PARM ← WRK-YEAR-MONTH(ACCEPT +
|
||
IF = SPACES 默认 '202605')"""
|
||
return {
|
||
'DBV-EMP-ID': [{'type': 'move', 'source_vars': ['R02EMP-ID']}],
|
||
'DBV-YEAR-MONTH': [{'type': 'move', 'source_vars': ['CNS-YEAR-MONTH-PARM']}],
|
||
'CNS-YEAR-MONTH-PARM': [{'type': 'move', 'source_vars': ['WRK-YEAR-MONTH']}],
|
||
'WRK-YEAR-MONTH': [{'type': 'accept', 'from': 'USER'},
|
||
{'type': 'move_literal', 'literal': '202605'}],
|
||
}
|
||
|
||
|
||
def test_insert_collision_composite_pk_matches_runtime_keys():
|
||
"""复合主键碰撞行必须等于运行时 INSERT 键:
|
||
EMP-ID 用输入记录键(R02EMP-ID);YEAR-MONTH 用 ACCEPT 后
|
||
IF = SPACES 的默认字面量('202605'),而非记录中的合成值('L00007')。
|
||
|
||
运行时 INSERT 键是 (A0000007, '202605'),碰撞行若用合成 YEAR-MONTH
|
||
则永不碰撞,-803 分支永远不可达。
|
||
"""
|
||
branch_paths = [
|
||
([('SQLCODE', '=', '-803', True)], {}),
|
||
]
|
||
records = [{
|
||
'R02EMP-ID': 'A0000007',
|
||
'DBV-EMP-ID': 'M0000007',
|
||
'DBV-YEAR-MONTH': 'N00007',
|
||
'WRK-YEAR-MONTH': 'L00007',
|
||
}]
|
||
db_input = build_db_input(
|
||
branch_paths, [], _kyu06_assignments(), _sql_meta_insert_salary_results(),
|
||
_declared_salary_results(),
|
||
records=records,
|
||
insert_pk={'SALARY-RESULTS': ['EMP_ID', 'YEAR_MONTH'],
|
||
'SALARY_RESULTS': ['EMP_ID', 'YEAR_MONTH']},
|
||
)
|
||
rows = db_input.get('SALARY-RESULTS', [])
|
||
assert len(rows) == 1
|
||
assert rows[0]['EMP-ID'] == 'A0000007'
|
||
assert rows[0]['YEAR-MONTH'] == '202605'
|
||
|
||
|
||
def test_insert_collision_year_month_not_falling_back_to_emp_id():
|
||
"""无 MOVE 链/ACCEPT 默认时,YEAR-MONTH 碰撞列不应回退到 EMP-ID 输入字段
|
||
(旧 bug 会取 R02EMP-ID 截断成 'A00000'),而应使用宿主变量自身记录值。"""
|
||
branch_paths = [
|
||
([('SQLCODE', '=', '-803', True)], {}),
|
||
]
|
||
records = [{'R02EMP-ID': 'A0000007', 'DBV-EMP-ID': 'M0000007',
|
||
'DBV-YEAR-MONTH': 'N00007'}]
|
||
db_input = build_db_input(
|
||
branch_paths, [], {}, _sql_meta_insert_salary_results(),
|
||
_declared_salary_results(),
|
||
records=records,
|
||
insert_pk={'SALARY-RESULTS': ['EMP_ID', 'YEAR_MONTH'],
|
||
'SALARY_RESULTS': ['EMP_ID', 'YEAR_MONTH']},
|
||
)
|
||
rows = db_input.get('SALARY-RESULTS', [])
|
||
assert len(rows) == 1
|
||
assert rows[0]['YEAR-MONTH'] == 'N00007'
|