Files
cobol-java-v3/tests/test_kin08dbu_fixes.py

170 lines
7.4 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""KIN08DBU DB 管道修复测试。
覆盖:
- Fix A: _scan_assign_to 引号 ASSIGN 捕获(阻断运行回归)
- Fix B: _inject_sql_error_rows 目标实际 INSERT 记录(9100DBERRSOR 可达)
- Fix D: 聚合边界数据注入(overflow / agg table full
"""
import os
import sys
import tempfile
from pathlib import Path
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
import pytest
from orchestrator_db import GixsqlOrchestrator
def _make_orch(src_text: str):
orch = object.__new__(GixsqlOrchestrator)
tmp = Path(tempfile.mkdtemp())
orch.src_path = tmp / "prog.cbl"
orch.src_path.write_text(src_text, encoding="utf-8-sig")
orch.work_dir = tmp / "work"
return orch, tmp
KIN08_SRC = """ IDENTIFICATION DIVISION.
PROGRAM-ID. KIN08DBU.
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT R01INNFIL ASSIGN TO "KIN08R01".
SELECT SYSINFILE ASSIGN TO "KIN08S01".
SELECT W01OUTFIL ASSIGN TO "KIN08W01".
DATA DIVISION.
FILE SECTION.
FD R01INNFIL.
01 R01INNREC.
PROCEDURE DIVISION.
OPEN INPUT R01INNFIL.
OPEN INPUT SYSINFILE.
OPEN OUTPUT W01OUTFIL.
"""
def test_scan_assign_to_does_not_capture_quote():
"""带引号 ASSIGN TO 应返回不带引号的文件名(Fix A 回归)。"""
orch, _ = _make_orch(KIN08_SRC)
assign_map = orch._scan_assign_to()
assert assign_map.get('KIN08S01') == 'INPUT', f'KIN08S01 应为 INPUT, 实际: {assign_map}'
assert assign_map.get('KIN08R01') == 'INPUT'
assert assign_map.get('KIN08W01') == 'OUTPUT'
# 禁止出现带前导引号的键
assert not any(k.startswith('"') for k in assign_map), f'键不应含引号: {assign_map}'
def test_make_synthetic_error_rows_skips_blanked_first_record():
"""合成错误行应跳过 records[0](其 EMP-ID 会被清空、运行时不会 INSERT)。"""
orch, _ = _make_orch(KIN08_SRC)
table = type('T', (), {
'name': 'DAILY_RECORDS',
'columns': [
type('C', (), {'name': 'EMP_ID', 'primary_key': True}),
type('C', (), {'name': 'TARGET_DATE', 'primary_key': True}),
type('C', (), {'name': 'TIME_IN', 'primary_key': False}),
],
})()
records = [
{'R01EMP-ID': ' ' * 8, 'R01DATE': '20000101', 'R01TIME-IN': '0900', 'HV-EMP-ID': ' ' * 8, 'HV-TARGET-DATE': '20000101'},
{'R01EMP-ID': '00000102', 'R01DATE': '20000102', 'R01TIME-IN': '0900', 'HV-EMP-ID': '00000102', 'HV-TARGET-DATE': '20000102'},
{'R01EMP-ID': '00000103', 'R01DATE': '20000103', 'R01TIME-IN': '0900', 'HV-EMP-ID': '00000103', 'HV-TARGET-DATE': '20000103'},
]
rows = orch._make_synthetic_error_rows(table, records)
assert rows, '应生成合成错误行'
# 第一行不应是 records[0](空 EMP-ID 记录)
assert rows[0][0].strip() != '', f'首行 EMP_ID 不应为空白: {rows[0]}'
assert rows[0][0] == '00000102', f'首行应为 records[1] 的 EMP_ID: {rows[0]}'
def test_inject_sql_error_rows_uses_per_row_values():
"""inject 循环内应逐行使用 row 值(而非固定 rows[0])。"""
orch, tmp = _make_orch(KIN08_SRC)
table = type('T', (), {
'name': 'DAILY_RECORDS',
'columns': [
type('C', (), {'name': 'EMP_ID', 'primary_key': True}),
type('C', (), {'name': 'TARGET_DATE', 'primary_key': True}),
type('C', (), {'name': 'TIME_IN', 'primary_key': False}),
],
})()
orch.schema = type('S', (), {'db_tables': [table]})()
db = tmp / 'kin.db'
import sqlite3
conn = sqlite3.connect(str(db))
conn.execute('CREATE TABLE [DAILY_RECORDS] ([EMP_ID] CHAR(8), [TARGET_DATE] CHAR(8), [TIME_IN] CHAR(4), PRIMARY KEY ([EMP_ID], [TARGET_DATE]))')
conn.commit()
conn.close()
records = [
{'R01EMP-ID': '00000101', 'R01DATE': '20000101', 'R01TIME-IN': '0900', 'HV-EMP-ID': '00000101', 'HV-TARGET-DATE': '20000101'},
{'R01EMP-ID': '00000102', 'R01DATE': '20000102', 'R01TIME-IN': '0900', 'HV-EMP-ID': '00000102', 'HV-TARGET-DATE': '20000102'},
{'R01EMP-ID': '00000103', 'R01DATE': '20000103', 'R01TIME-IN': '0900', 'HV-EMP-ID': '00000103', 'HV-TARGET-DATE': '20000103'},
]
orch._inject_sql_error_rows(db, records)
conn = sqlite3.connect(str(db))
rows = conn.execute('SELECT EMP_ID FROM [DAILY_RECORDS]').fetchall()
conn.close()
emp_ids = {r[0] for r in rows}
# 至少注入 1 条与 records[1] 匹配的 PK(运行时将被 INSERT → 冲突)
assert '00000102' in emp_ids, f'应注入 records[1] 的 PK, 实际: {emp_ids}'
def test_format_value_numeric_field_non_numeric_writes_spaces():
"""数字字段收到非数字值不应被静默转成 0(会造成 '00000000' PK 冲突),应写 SPACE。"""
from cobol_testgen.flatfile import _format_value
field = {'name': 'R01EMP-ID', 'type': 'numeric', 'length': 8,
'pic': '9(008)', 'usage': None,
'pic_info': {'type': 'numeric', 'digits': 8, 'decimal': 0,
'length': 8, 'signed': False}}
# 非数字值 → SPACE(程序空社員チェックでスキップ)
assert _format_value('U0000031', field) == b' ' * 8, \
f'非数字 EMP 应写 SPACE, 实际 {_format_value("U0000031", field)!r}'
# 数字值 → 正常 zfill
assert _format_value('102', field) == b'00000102'
# 全ゼロ → 保持(由 sanitize 负责 blank
assert _format_value('00000000', field) == b'00000000'
def test_inject_aggregation_boundaries():
"""聚合边界注入:overflow 同键大小时数 + agg-full 超 100 组合(数字 EMP 字段)。"""
orch, tmp = _make_orch(KIN08_SRC)
recs = []
for i in range(60):
recs.append({
'R01EMP-ID': f'00000{i + 100}', 'R01DATE': f'{20000101 + i:08d}',
'R01TIME-IN': '0900', 'R01TIME-OUT': '1800',
'R01ANNUAL-H': '00010', 'R01PERSONAL-H': '00000',
'R01OFFICIAL-H': '00000', 'R01SICK-H': '00000', 'R01ABSENT-H': '00000',
'R01FILLER': ' ' * 151,
})
# 字母型 EMP(数字字段的非法值)不应被选为 dup_eid
recs.append({'R01EMP-ID': 'U0000031', 'R01DATE': '20001231',
'R01TIME-IN': '0900', 'R01TIME-OUT': '1800',
'R01ANNUAL-H': '00010', 'R01PERSONAL-H': '00000',
'R01OFFICIAL-H': '00000', 'R01SICK-H': '00000', 'R01ABSENT-H': '00000',
'R01FILLER': ' ' * 151})
orch._inject_aggregation_boundaries(recs)
# overflow: 至少 2 条同 (EMP, 月) 且 ANNUAL-H 为最大值
pairs = {}
for r in recs:
if r.get('R01ANNUAL-H'):
ym = r['R01DATE'][:6]
pairs.setdefault((r['R01EMP-ID'], ym), []).append(r['R01ANNUAL-H'])
max_seen = max('9' * len(v[0]) for v in pairs.values() if v)
over = [(k, v) for k, v in pairs.items() if len(v) >= 2 and all(h == max_seen for h in v)]
assert over, f'应存在同键 overflow 组, pairs={pairs}'
# 追加记录的 EMP 必须为数字(否则 9(008) 字段写文件会变 SPACE
for r in recs[60:]:
assert str(r['R01EMP-ID']).isdigit(), f'追加记录 EMP 应为数字: {r["R01EMP-ID"]!r}'
# agg-full: 不同 (EMP, 月) 组合 >= 101
distinct = set()
for r in recs:
distinct.add((r['R01EMP-ID'], r['R01DATE'][:6]))
assert len(distinct) >= 101, f'应有 >=101 个聚合键, 实际 {len(distinct)}'