diff --git a/paperforge/worker/repair.py b/paperforge/worker/repair.py index 11492907..ad3b1e46 100644 --- a/paperforge/worker/repair.py +++ b/paperforge/worker/repair.py @@ -220,8 +220,8 @@ def run_repair(vault: Path, paths: dict, verbose: bool = False, fix: bool = Fals idx_status = entry.get("ocr_status", "") index_ocr_status = str(idx_status).strip().lower() if idx_status else None break - except Exception: - pass + except Exception as e: + logger.warning("Failed to load index for %s: %s", zotero_key, e) meta_path = paths["ocr"] / zotero_key / "meta.json" meta_ocr_status = None meta_validated_status = None @@ -303,8 +303,8 @@ def run_repair(vault: Path, paths: dict, verbose: bool = False, fix: bool = Fals index_data["items"] = idx_items write_json(paths["index"], index_data) fixed_index_entry = True - except Exception: - pass + except Exception as e: + logger.warning("Failed to update index entry for %s: %s", zotero_key, e) if meta_validated_status is not None and meta_validated_status != "done": if meta_path.exists(): try: @@ -312,8 +312,8 @@ def run_repair(vault: Path, paths: dict, verbose: bool = False, fix: bool = Fals meta["ocr_status"] = "pending" write_json(meta_path, meta) fixed_meta = True - except Exception: - pass + except Exception as e: + logger.warning("Failed to reset meta ocr_status for %s: %s", zotero_key, e) record_do_ocr_match = re.search(r"^do_ocr:\s*(true|false)$", new_record_text, re.MULTILINE) is_do_ocr = record_do_ocr_match and record_do_ocr_match.group(1) == "true" if not is_do_ocr: @@ -344,16 +344,16 @@ def run_repair(vault: Path, paths: dict, verbose: bool = False, fix: bool = Fals index_data["items"] = idx_items write_json(paths["index"], index_data) fixed_index_entry = True - except Exception: - pass + except Exception as e: + logger.warning("Failed to update index entry for %s: %s", zotero_key, e) if meta_path.exists(): try: meta = read_json(meta_path) meta["ocr_status"] = "pending" write_json(meta_path, meta) fixed_meta = True - except Exception: - pass + except Exception as e: + logger.warning("Failed to reset meta ocr_status for %s: %s", zotero_key, e) record_do_ocr_match = re.search(r"^do_ocr:\s*(true|false)$", new_record_text, re.MULTILINE) is_do_ocr = record_do_ocr_match and record_do_ocr_match.group(1) == "true" if not is_do_ocr: @@ -361,6 +361,8 @@ def run_repair(vault: Path, paths: dict, verbose: bool = False, fix: bool = Fals if final_record_text != new_record_text: record_path.write_text(final_record_text, encoding="utf-8") fixed_formal_note_primary = True + else: + print(f"[WARNING] No --fix handler for {zotero_key}: {div_reason}") fixed_count = sum([fixed_formal_note_primary, fixed_index_entry, fixed_meta]) result["fixed"] += fixed_count if verbose and fixed_count > 0: diff --git a/tests/test_repair.py b/tests/test_repair.py index 96318113..a55e190b 100644 --- a/tests/test_repair.py +++ b/tests/test_repair.py @@ -501,6 +501,105 @@ class TestRepairCommandPaths: assert "config" in captured["paths"] +class TestRepairExceptionLogging: + """REPAIR-03: Verify bare except:pass blocks replaced with logger.warning().""" + + def test_index_load_failure_logs_warning(self, tmp_path, caplog): + """Index load exception at line 223 logs warning.""" + import logging + caplog.set_level(logging.WARNING, logger="paperforge.worker.repair") + vault = _make_vault(tmp_path) + paths = pipeline_paths(vault) + _write_formal_note(paths["literature"], "KEY001", "骨科", "pending") + # Corrupt the index file to trigger read failure + paths["index"].parent.mkdir(parents=True, exist_ok=True) + paths["index"].write_text("{corrupt_json}", encoding="utf-8") + run_repair(vault, paths, verbose=False, fix=False) + assert "Failed to load index" in caplog.text + assert "KEY001" in caplog.text + + def test_index_write_failure_first_branch_logs_warning(self, tmp_path, caplog, monkeypatch): + """Index write exception in first --fix branch at line 306 logs warning.""" + import logging + import paperforge.worker.repair as repair_mod + caplog.set_level(logging.WARNING, logger="paperforge.worker.repair") + + def _failing_write(_path, _data): + raise OSError("Simulated write failure") + + monkeypatch.setattr(repair_mod, "write_json", _failing_write) + + vault = _make_vault(tmp_path) + paths = pipeline_paths(vault) + _write_formal_note(paths["literature"], "KEY001", "骨科", "done", do_ocr="false") + # Trigger condition 1 (done_incomplete) via minimal meta + _write_minimal_meta(paths["ocr"], "KEY001", "done") + # Create valid index with entry so index_ocr_status is set + paths["index"].parent.mkdir(parents=True, exist_ok=True) + paths["index"].write_text(json.dumps({"items": [{"zotero_key": "KEY001", "ocr_status": "done"}]}), encoding="utf-8") + run_repair(vault, paths, verbose=False, fix=True) + assert "Failed to update index" in caplog.text + assert "KEY001" in caplog.text + + def test_index_write_failure_second_branch_logs_warning(self, tmp_path, caplog, monkeypatch): + """Index write exception in second --fix branch at line 347 logs warning.""" + import logging + import paperforge.worker.repair as repair_mod + caplog.set_level(logging.WARNING, logger="paperforge.worker.repair") + + def _failing_write(_path, _data): + raise OSError("Simulated write failure") + + monkeypatch.setattr(repair_mod, "write_json", _failing_write) + + vault = _make_vault(tmp_path) + paths = pipeline_paths(vault) + _write_formal_note(paths["literature"], "KEY001", "骨科", "done", do_ocr="false") + # Trigger condition 2 (note done, meta pending) via write_meta + _write_meta(paths["ocr"], "KEY001", "pending") + # Create valid index with entry + paths["index"].parent.mkdir(parents=True, exist_ok=True) + paths["index"].write_text(json.dumps({"items": [{"zotero_key": "KEY001", "ocr_status": "done"}]}), encoding="utf-8") + run_repair(vault, paths, verbose=False, fix=True) + assert "Failed to update index" in caplog.text + assert "KEY001" in caplog.text + + def test_meta_write_failure_logs_warning(self, tmp_path, caplog, monkeypatch): + """Meta write exception in second --fix branch at line 355 logs warning.""" + import logging + import paperforge.worker.repair as repair_mod + caplog.set_level(logging.WARNING, logger="paperforge.worker.repair") + + def _failing_write(_path, _data): + raise OSError("Simulated meta write failure") + + monkeypatch.setattr(repair_mod, "write_json", _failing_write) + + vault = _make_vault(tmp_path) + paths = pipeline_paths(vault) + _write_formal_note(paths["literature"], "KEY001", "骨科", "done", do_ocr="false") + meta_path = _write_meta(paths["ocr"], "KEY001", "pending") + # No index needed (no index_ocr_status, skips index write, goes to meta write) + run_repair(vault, paths, verbose=False, fix=True) + assert "Failed to reset meta" in caplog.text + assert "KEY001" in caplog.text + + +class TestRepairFixModeWarning: + """REPAIR-02: --fix mode else clause warns on unhandled types.""" + + def test_unhandled_divergence_prints_warning(self, tmp_path, capsys): + """Condition 4 divergence (note pending vs meta done) with no --fix handler prints [WARNING].""" + vault = _make_vault(tmp_path) + paths = pipeline_paths(vault) + _write_formal_note(paths["literature"], "KEY001", "骨科", "pending") + _write_meta(paths["ocr"], "KEY001", "done", page_count=10) + run_repair(vault, paths, verbose=False, fix=True) + captured = capsys.readouterr() + assert "[WARNING]" in captured.out + assert "KEY001" in captured.out + + class TestRepairDeadCode: """REPAIR-04: Verify load_domain_config dead code removed."""