279 lines
10 KiB
Python
279 lines
10 KiB
Python
import os
|
||
import pytest
|
||
from agent.rule_loader import RuleLoader, PGM_PATTERN_MAP
|
||
from agent.models import ProgramMeta
|
||
|
||
RULES_DIR = os.path.join(os.path.dirname(__file__), '..', 'rules')
|
||
|
||
ALL_35_TYPES = [
|
||
'マッチング(1:1)',
|
||
'マッチング(1:N)',
|
||
'マッチング(N:1)',
|
||
'レイアウト編集のみ(GETPUT)',
|
||
'振り分け(IF文)',
|
||
'振り分け(EVALUATE文)',
|
||
'キーブレイク(集計)',
|
||
'キーブレイク(集約)',
|
||
'DB更新',
|
||
'50分割',
|
||
'25分割',
|
||
'100分割',
|
||
'項目チェック(重複含まず)',
|
||
'オンラインPGM',
|
||
'CSV→FB変換(改行なし)',
|
||
'2段階マッチング(1:1⇒1:1)',
|
||
'2段階マッチング(N:1⇒N:1)',
|
||
'マッチングM:N⇒出力M件',
|
||
'マッチングM:N⇒出力N件',
|
||
'マッチングM:N⇒出力M×N件',
|
||
'CSV→FB変換(改行あり)',
|
||
'2段階マッチング(M:N⇒M:N)',
|
||
'SELECT条件',
|
||
'内部テーブル検索',
|
||
'サブプログラム使用',
|
||
'DB検索',
|
||
'項目チェック(半角20桁/4桁)',
|
||
'SYSIN読込',
|
||
'ASCII→EBCDIC変換',
|
||
'キーブレイク(集計集約以外)',
|
||
'項目チェック(重複含む)',
|
||
'1:N+キーブレイク(同キー)',
|
||
'1:N+キーブレイク(異キー)',
|
||
'SORT(INPUT/OUTPUT PROCEDURE)',
|
||
'MERGE(複数ファイル結合)',
|
||
]
|
||
|
||
DETAILED_DESIGN_PATTERNS = [
|
||
'29(ASCII→EBCDIC変換)',
|
||
'18(M:N→M件マッチング)',
|
||
'20(M:N→M×N件直積出力)',
|
||
'マッチング(1:1→1:1 2段階)',
|
||
'マッチング(N:1→N:1 2段階)',
|
||
'マッチング(M:N→M:N 2段階)',
|
||
'33(1:N+異キーキーブレイク)',
|
||
'34(SORT INPUT/OUTPUT PROCEDURE)',
|
||
'11(25分割)',
|
||
'12(100分割)',
|
||
'27(半角20桁/4桁チェック)',
|
||
'19(M:N→N件マッチング+集計)',
|
||
'DB更新 + SYSIN読込(P28)',
|
||
'編集出力(ランキング生成)',
|
||
'32(1:N+同キーキーブレイク)',
|
||
'編集出力(印刷制御)',
|
||
]
|
||
|
||
|
||
def test_pgm_pattern_map_has_known_patterns():
|
||
assert 'マッチング(1:1)' in PGM_PATTERN_MAP
|
||
assert 'マッチング(1:N)' in PGM_PATTERN_MAP
|
||
assert 'DB更新' in PGM_PATTERN_MAP
|
||
|
||
|
||
@pytest.mark.parametrize('pattern', ALL_35_TYPES)
|
||
def test_all_classification_types_loadable(pattern):
|
||
"""程序分类说明.md の35类型すべてが規則に解決できること。"""
|
||
loader = RuleLoader(RULES_DIR)
|
||
assert loader._load_pgm_pattern_rule(pattern) is not None, f'未実装: {pattern}'
|
||
|
||
|
||
@pytest.mark.parametrize('pattern', DETAILED_DESIGN_PATTERNS)
|
||
def test_detailed_design_pattern_names_loadable(pattern):
|
||
"""詳細設計書の PGMパターン/プログラムタイプ 表記が規則に解決できること。"""
|
||
loader = RuleLoader(RULES_DIR)
|
||
assert loader._load_pgm_pattern_rule(pattern) is not None, f'マッピング不足: {pattern}'
|
||
|
||
|
||
def test_non_aggregate_keybreak_uses_dedicated_rule():
|
||
loader = RuleLoader(RULES_DIR)
|
||
rule = loader._load_pgm_pattern_rule('キーブレイク(集計集約以外)')
|
||
assert rule is not None
|
||
assert '非集計' in rule
|
||
|
||
|
||
def test_two_stage_matching_split_by_form():
|
||
loader = RuleLoader(RULES_DIR)
|
||
r1 = loader._load_pgm_pattern_rule('2段階マッチング(1:1⇒1:1)')
|
||
r2 = loader._load_pgm_pattern_rule('2段階マッチング(N:1⇒N:1)')
|
||
r3 = loader._load_pgm_pattern_rule('マッチング(M:N→M:N 2段階)')
|
||
assert r1 is not None and r2 is not None and r3 is not None
|
||
assert '1:1⇒1:1' in r1
|
||
assert 'N:1⇒N:1' in r2
|
||
assert 'M:N⇒M:N' in r3
|
||
assert r1 != r2 and r2 != r3 and r1 != r3
|
||
|
||
|
||
def test_load_matching_1_1_rule():
|
||
loader = RuleLoader(RULES_DIR)
|
||
meta = ProgramMeta(
|
||
program_id='TEST', program_name='', system_name='',
|
||
pgm_type='メイン', pgm_pattern='マッチング(1:1)',
|
||
summary_lines=[], prerequisites=[], files=[], keys=[], modules=[],
|
||
process_detail='', output_records='',
|
||
input_type='file', copy_fields={}, db_tables={}
|
||
)
|
||
combined, descriptions, count = loader.load(meta)
|
||
|
||
assert count > 0
|
||
assert len(combined) > 0
|
||
assert 'マッチング(1:1)' in combined
|
||
assert '数据生成' in combined
|
||
|
||
|
||
def test_load_matching_1_n_rule():
|
||
loader = RuleLoader(RULES_DIR)
|
||
meta = ProgramMeta(
|
||
program_id='TEST', program_name='', system_name='',
|
||
pgm_type='メイン', pgm_pattern='マッチング(1:N)',
|
||
summary_lines=[], prerequisites=[], files=[], keys=[], modules=[],
|
||
process_detail='', output_records='',
|
||
input_type='file', copy_fields={}, db_tables={}
|
||
)
|
||
combined, descriptions, count = loader.load(meta)
|
||
|
||
assert count > 0
|
||
assert len(combined) > 0
|
||
assert 'マッチング(1:N)' in combined
|
||
|
||
|
||
def test_matching_n_1_has_only_n_1_imbalance():
|
||
loader = RuleLoader(RULES_DIR)
|
||
rule = loader._load_pgm_pattern_rule('マッチング(N:1)')
|
||
assert rule is not None
|
||
_, count = loader._parse_group_info(rule)
|
||
assert count == 9
|
||
assert 'N:1匹配(MT-N003)' in rule or 'N:1匹配(MT-N003)' in rule
|
||
assert '极端不平衡 N:1' in rule
|
||
assert '极端不平衡 1:N' not in rule
|
||
|
||
|
||
def test_if_branching_rule_covers_if_benchmark():
|
||
loader = RuleLoader(RULES_DIR)
|
||
rule = loader._load_pgm_pattern_rule('振り分け(IF文)')
|
||
assert rule is not None
|
||
_, count = loader._parse_group_info(rule)
|
||
assert count == 5
|
||
for marker in ('B-N001', 'B-N005', 'IF 2路分支'):
|
||
assert marker in rule
|
||
assert 'B-N006' not in rule
|
||
assert 'B-N010' not in rule
|
||
assert 'B-R001' not in rule
|
||
|
||
|
||
def test_evaluate_branching_rule_covers_evaluate_benchmark():
|
||
loader = RuleLoader(RULES_DIR)
|
||
rule = loader._load_pgm_pattern_rule('振り分け(EVALUATE文)')
|
||
assert rule is not None
|
||
_, count = loader._parse_group_info(rule)
|
||
assert count == 5
|
||
for marker in ('B-N006', 'B-N009', 'B-A002', 'EVALUATE WHEN 多值'):
|
||
assert marker in rule
|
||
assert 'B-N001' not in rule
|
||
assert 'B-N010' not in rule
|
||
assert 'B-R001' not in rule
|
||
|
||
|
||
def test_keybreak_summary_rule_covers_benchmark():
|
||
loader = RuleLoader(RULES_DIR)
|
||
rule = loader._load_pgm_pattern_rule('キーブレイク(集計)')
|
||
assert rule is not None
|
||
_, count = loader._parse_group_info(rule)
|
||
assert count == 7
|
||
for marker in ('KB-N001', 'KB-N004', 'KB-N005', 'KB-N006', 'KB-A001', 'KB-A002', 'KB-R001'):
|
||
assert marker in rule
|
||
assert 'KB-N002' not in rule
|
||
|
||
|
||
def test_keybreak_aggregation_rule_covers_benchmark():
|
||
loader = RuleLoader(RULES_DIR)
|
||
rule = loader._load_pgm_pattern_rule('キーブレイク(集約)')
|
||
assert rule is not None
|
||
_, count = loader._parse_group_info(rule)
|
||
assert count == 7
|
||
for marker in ('KB-N002', 'KB-N004', 'KB-N005', 'KB-N006', 'KB-A001', 'KB-A002', 'KB-R001'):
|
||
assert marker in rule
|
||
assert 'KB-N001' not in rule
|
||
|
||
|
||
def test_combined_keybreak_rule_removed_and_remapped():
|
||
combined = os.path.join(RULES_DIR, 'pgm_pattern', 'キーブレイク(集計、集約).md')
|
||
assert not os.path.exists(combined)
|
||
|
||
loader = RuleLoader(RULES_DIR)
|
||
summary = loader._load_pgm_pattern_rule('キーブレイク(集計)')
|
||
for pattern in ('キーブレイク(集計、集約)', '1:N+キーブレイク(同キー)',
|
||
'1:Nキーブレイク(同キー集約)', '32(1:N+同キーキーブレイク)'):
|
||
rule = loader._load_pgm_pattern_rule(pattern)
|
||
assert rule is not None
|
||
assert rule == summary
|
||
|
||
|
||
def test_item_check_no_dup_rule_covers_benchmark():
|
||
loader = RuleLoader(RULES_DIR)
|
||
rule = loader._load_pgm_pattern_rule('項目チェック(重複含まず)')
|
||
assert rule is not None
|
||
_, count = loader._parse_group_info(rule)
|
||
assert count == 5
|
||
for marker in ('VF-N001', 'VF-N002', 'VF-N003', 'VF-N004', 'VF-R001'):
|
||
assert marker in rule
|
||
assert 'VF-N005' not in rule
|
||
assert not os.path.exists(os.path.join(RULES_DIR, 'pgm_pattern', '項目チェック.md'))
|
||
generic = loader._load_pgm_pattern_rule('項目チェック')
|
||
assert generic == rule
|
||
|
||
|
||
def test_item_check_halfwidth_rule_covers_benchmark():
|
||
loader = RuleLoader(RULES_DIR)
|
||
rule = loader._load_pgm_pattern_rule('項目チェック(半角20桁/4桁)')
|
||
assert rule is not None
|
||
_, count = loader._parse_group_info(rule)
|
||
assert count == 4
|
||
for marker in ('VF-N005', 'VF-N006', 'VF-A001', 'VF-A002'):
|
||
assert marker in rule
|
||
assert 'VF-N001' not in rule
|
||
|
||
|
||
def test_load_nonexistent_pattern_raises():
|
||
loader = RuleLoader(RULES_DIR)
|
||
meta = ProgramMeta(
|
||
program_id='TEST', program_name='', system_name='',
|
||
pgm_type='メイン', pgm_pattern='存在しないパターン',
|
||
summary_lines=[], prerequisites=[], files=[], keys=[], modules=[],
|
||
process_detail='', output_records='',
|
||
input_type='file', copy_fields={}, db_tables={}
|
||
)
|
||
try:
|
||
loader.load(meta)
|
||
assert False, 'Should have raised FileNotFoundError'
|
||
except FileNotFoundError:
|
||
pass
|
||
|
||
|
||
def test_detect_conditional_branch():
|
||
process = """
|
||
2-1-2.ロジック分岐判定(EVALUATE)
|
||
2-1-2-1.STATUS='1'の場合
|
||
INSERT処理
|
||
"""
|
||
assert RuleLoader._detect_feature(process, ['場合', 'EVALUATE', 'IF']) is True
|
||
|
||
|
||
def test_no_keyword_match():
|
||
process = "1.初期処理\n2.主処理\n3.終了処理"
|
||
assert RuleLoader._detect_feature(process, ['EVALUATE']) is False
|
||
|
||
|
||
def test_rule_with_conditional_feature():
|
||
loader = RuleLoader(RULES_DIR)
|
||
meta = ProgramMeta(
|
||
program_id='TEST', program_name='', system_name='',
|
||
pgm_type='メイン', pgm_pattern='マッチング(1:1)',
|
||
summary_lines=[], prerequisites=[], files=[], keys=[], modules=[],
|
||
process_detail='2-1-2.EVALUATEで条件分岐する。',
|
||
output_records='',
|
||
input_type='file', copy_fields={}, db_tables={}
|
||
)
|
||
combined, descriptions, count = loader.load(meta)
|
||
|
||
assert count > 0
|
||
assert '条件分支' in combined
|