189 lines
8.0 KiB
Python
189 lines
8.0 KiB
Python
"""SELECT BETWEEN WHERE 约束的 DB 预置行生成测试(Phase 2)。
|
||
|
||
根因:KYU05DED 的 `WHERE :WRK-TAXABLE-INCOME BETWEEN TAX-FROM AND TAX-TO`
|
||
在修复解析后成为 between 约束,但 build_db_input 不处理 between 类型,
|
||
TAX_FROM/TAX_TO 仍落入默认序号值 → 运行时 SELECT 查不到行 → SQLCODE<>0。
|
||
修复:build_db_input 对 between 约束,用记录中的 subject 值播种 lo/hi 列,
|
||
使运行时 `:hv BETWEEN TAX-FROM AND TAX-TO` 能命中。
|
||
"""
|
||
|
||
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_select_between():
|
||
return [{
|
||
"type": "exec_sql_select",
|
||
"table": "TAX-TABLE",
|
||
"select_list": "TAX-RATE, DEDUCTION",
|
||
"into_vars": ["DBV-TAX-RATE", "DBV-TAX-DEDUCTION"],
|
||
"where": ":WRK-TAXABLE-INCOME BETWEEN TAX-FROM AND TAX-TO",
|
||
"where_constraints": [
|
||
{"type": "between", "neg": False, "op": "BETWEEN",
|
||
"subject": {"kind": "host_var", "name": "WRK-TAXABLE-INCOME"},
|
||
"lo": {"kind": "column", "name": "TAX-FROM"},
|
||
"hi": {"kind": "column", "name": "TAX-TO"}},
|
||
],
|
||
}]
|
||
|
||
|
||
def _declared_tax_table():
|
||
return {
|
||
"TAX-TABLE": [
|
||
{"name": "TAX_FROM", "db_type": "DECIMAL"},
|
||
{"name": "TAX_TO", "db_type": "DECIMAL"},
|
||
{"name": "TAX_RATE", "db_type": "DECIMAL"},
|
||
{"name": "DEDUCTION", "db_type": "DECIMAL"},
|
||
],
|
||
}
|
||
|
||
|
||
def test_select_between_hostvar_subject_seeds_lo_hi_from_record():
|
||
"""`:hv BETWEEN TAX-FROM AND TAX-TO`:TAX_FROM/TAX_TO 播成记录中
|
||
WRK-TAXABLE-INCOME 的值,保证运行时 BETWEEN 命中。"""
|
||
records = [{"WRK-TAXABLE-INCOME": "000000000"}]
|
||
db_input = build_db_input(
|
||
[([('SQLCODE', '=', '0', True)], {})], [], {},
|
||
_sql_meta_select_between(), _declared_tax_table(), records=records)
|
||
rows = db_input.get('TAX-TABLE', [])
|
||
assert len(rows) == 1
|
||
assert int(rows[0]['TAX_FROM']) == 0
|
||
assert int(rows[0]['TAX_TO']) == 0
|
||
|
||
|
||
def test_select_between_hostvar_subject_nonzero():
|
||
"""subject 非零时,lo/hi 取记录值。"""
|
||
records = [{"WRK-TAXABLE-INCOME": "000102400"}]
|
||
db_input = build_db_input(
|
||
[([('SQLCODE', '=', '0', True)], {})], [], {},
|
||
_sql_meta_select_between(), _declared_tax_table(), records=records)
|
||
rows = db_input.get('TAX-TABLE', [])
|
||
assert len(rows) == 1
|
||
assert int(rows[0]['TAX_FROM']) == 102400
|
||
assert int(rows[0]['TAX_TO']) == 102400
|
||
|
||
|
||
def test_select_between_hostvar_subject_moved_from_input():
|
||
"""subject 主机变量经 MOVE 链到输入键时,优先用输入键值。"""
|
||
sql_meta = [{
|
||
"type": "exec_sql_select",
|
||
"table": "TAX-TABLE",
|
||
"select_list": "TAX-RATE, DEDUCTION",
|
||
"into_vars": ["DBV-TAX-RATE", "DBV-TAX-DEDUCTION"],
|
||
"where": ":WRK-TAXABLE-INCOME BETWEEN TAX-FROM AND TAX-TO",
|
||
"where_constraints": [
|
||
{"type": "between", "neg": False, "op": "BETWEEN",
|
||
"subject": {"kind": "host_var", "name": "WRK-TAXABLE-INCOME"},
|
||
"lo": {"kind": "column", "name": "TAX-FROM"},
|
||
"hi": {"kind": "column", "name": "TAX-TO"}},
|
||
],
|
||
}]
|
||
records = [{"R01GROSS-PAYMENT": "000102400",
|
||
"WRK-TAXABLE-INCOME": "000000000"}]
|
||
assignments = {
|
||
'WRK-TAXABLE-INCOME': [{'type': 'move', 'source_vars': ['R01GROSS-PAYMENT']}],
|
||
}
|
||
db_input = build_db_input(
|
||
[([('SQLCODE', '=', '0', True)], {})], [], assignments,
|
||
sql_meta, _declared_tax_table(), records=records)
|
||
rows = db_input.get('TAX-TABLE', [])
|
||
assert len(rows) == 1
|
||
assert int(rows[0]['TAX_FROM']) == 102400
|
||
|
||
|
||
def test_select_between_column_subject_seeds_subject_col():
|
||
"""`SALARY BETWEEN :LO AND :HI`:subject 是列时,把该列播成 lo 值
|
||
(满足 lo <= col <= hi)。"""
|
||
sql_meta = [{
|
||
"type": "exec_sql_select",
|
||
"table": "EMP",
|
||
"select_list": "EMP_NAME",
|
||
"into_vars": ["DBV-EMPLOYEE-NAME"],
|
||
"where": "SALARY BETWEEN :LO AND :HI",
|
||
"where_constraints": [
|
||
{"type": "between", "neg": False, "op": "BETWEEN",
|
||
"subject": {"kind": "column", "name": "SALARY"},
|
||
"lo": {"kind": "host_var", "name": "LO"},
|
||
"hi": {"kind": "host_var", "name": "HI"}},
|
||
],
|
||
}]
|
||
declared = {
|
||
"EMP": [
|
||
{"name": "SALARY", "db_type": "DECIMAL"},
|
||
{"name": "EMP_NAME", "db_type": "CHAR", "size": 40},
|
||
],
|
||
}
|
||
records = [{"LO": "000005000", "HI": "000009000"}]
|
||
db_input = build_db_input(
|
||
[([('SQLCODE', '=', '0', True)], {})], [], {},
|
||
sql_meta, declared, records=records)
|
||
rows = db_input.get('EMP', [])
|
||
assert len(rows) == 1
|
||
sal = int(rows[0]['SALARY'])
|
||
assert 5000 <= sal <= 9000
|
||
|
||
|
||
def test_select_between_sql_error_path_skips_row():
|
||
"""SQL 失败路径(SQLCODE<>0)仍不生成预置行。"""
|
||
records = [{"WRK-TAXABLE-INCOME": "000102400"}]
|
||
db_input = build_db_input(
|
||
[([('SQLCODE', '<>', '0', True)], {})], [], {},
|
||
_sql_meta_select_between(), _declared_tax_table(), records=records)
|
||
rows = db_input.get('TAX-TABLE', [])
|
||
assert not rows
|
||
|
||
|
||
def _fdict_with_ws_constants():
|
||
"""KYU05DED 简化的字段定义:CNS-TAX-BASIC-DEDUCTION 常量 + MOVE/COMPUTE 链。"""
|
||
return [
|
||
{'name': 'R01GROSS-PAYMENT', 'pic_info': {'type': 'numeric', 'digits': 9, 'decimal': 0, 'length': 0, 'signed': False}, 'section': 'INPUT', 'value': None},
|
||
{'name': 'WRK-GROSS-PAYMENT', 'pic_info': {'type': 'numeric', 'digits': 9, 'decimal': 0, 'length': 0, 'signed': False}, 'section': 'WORKING-STORAGE', 'value': None},
|
||
{'name': 'WRK-TAXABLE-INCOME', 'pic_info': {'type': 'numeric', 'digits': 9, 'decimal': 0, 'length': 0, 'signed': False}, 'section': 'WORKING-STORAGE', 'value': None},
|
||
{'name': 'CNS-TAX-BASIC-DEDUCTION', 'pic_info': {'type': 'numeric', 'digits': 6, 'decimal': 0, 'length': 0, 'signed': False}, 'section': 'WORKING-STORAGE', 'value': '50000'},
|
||
]
|
||
|
||
|
||
def test_select_between_derives_runtime_value_from_input_key():
|
||
"""记录中 WRK-TAXABLE-INCOME 为合成值时,按运行时推导值播种带。
|
||
|
||
输入 gross=000100000 → 运行时 taxable = gross - 50000 = 50000,
|
||
预置行 TAX_FROM/TAX_TO 应为 50000,而非记录的合成值。"""
|
||
assignments = {
|
||
'WRK-GROSS-PAYMENT': [{'type': 'move', 'source_vars': ['R01GROSS-PAYMENT']}],
|
||
'WRK-TAXABLE-INCOME': [
|
||
{'type': 'move_literal', 'literal': 'ZERO'},
|
||
{'type': 'compute', 'source_vars': ['WRK-GROSS-PAYMENT', 'CNS-TAX-BASIC-DEDUCTION'], 'op': '-'},
|
||
],
|
||
}
|
||
records = [{"R01GROSS-PAYMENT": "000100000",
|
||
"WRK-TAXABLE-INCOME": "000003701"}]
|
||
db_input = build_db_input(
|
||
[([('SQLCODE', '=', '0', True)], {})], _fdict_with_ws_constants(), assignments,
|
||
_sql_meta_select_between(), _declared_tax_table(), records=records)
|
||
rows = db_input.get('TAX-TABLE', [])
|
||
assert len(rows) == 1
|
||
assert int(rows[0]['TAX_FROM']) == 50000
|
||
assert int(rows[0]['TAX_TO']) == 50000
|
||
|
||
|
||
def test_select_between_derives_zero_for_low_gross():
|
||
"""gross < 基本控除(50000)时,运行时 taxable = 0 → 带为 [0,0]。"""
|
||
assignments = {
|
||
'WRK-GROSS-PAYMENT': [{'type': 'move', 'source_vars': ['R01GROSS-PAYMENT']}],
|
||
'WRK-TAXABLE-INCOME': [
|
||
{'type': 'move_literal', 'literal': 'ZERO'},
|
||
{'type': 'compute', 'source_vars': ['WRK-GROSS-PAYMENT', 'CNS-TAX-BASIC-DEDUCTION'], 'op': '-'},
|
||
],
|
||
}
|
||
records = [{"R01GROSS-PAYMENT": "000000501",
|
||
"WRK-TAXABLE-INCOME": "000003701"}]
|
||
db_input = build_db_input(
|
||
[([('SQLCODE', '=', '0', True)], {})], _fdict_with_ws_constants(), assignments,
|
||
_sql_meta_select_between(), _declared_tax_table(), records=records)
|
||
rows = db_input.get('TAX-TABLE', [])
|
||
assert len(rows) == 1
|
||
assert int(rows[0]['TAX_FROM']) == 0
|
||
assert int(rows[0]['TAX_TO']) == 0
|
||
|