|
|
|
@ -1,9 +1,12 @@ |
|
|
|
"""Synthetic checks for source-safe local import discovery.""" |
|
|
|
"""Synthetic checks for source-safe local import discovery.""" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import os |
|
|
|
from hashlib import sha256 |
|
|
|
from hashlib import sha256 |
|
|
|
|
|
|
|
from pathlib import Path |
|
|
|
|
|
|
|
|
|
|
|
from django.test import override_settings |
|
|
|
from django.test import override_settings |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
from ingestion import guided_imports |
|
|
|
from ingestion.guided_imports import discover_imports |
|
|
|
from ingestion.guided_imports import discover_imports |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -59,3 +62,66 @@ def test_discovery_ignores_symlinks_and_non_files(tmp_path): |
|
|
|
rows = discover_imports() |
|
|
|
rows = discover_imports() |
|
|
|
|
|
|
|
|
|
|
|
assert [row.name for row in rows] == ["target.csv"] |
|
|
|
assert [row.name for row in rows] == ["target.csv"] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_discovery_marks_a_post_listing_symlink_replacement_unreadable(tmp_path, monkeypatch): |
|
|
|
|
|
|
|
bank = HEADER + b"01/02/2026,,SYNTHETIC BANK ROW,,,12.34\n" |
|
|
|
|
|
|
|
candidate = tmp_path / "candidate.csv" |
|
|
|
|
|
|
|
replacement = tmp_path / "replacement.csv" |
|
|
|
|
|
|
|
candidate.write_bytes(bank) |
|
|
|
|
|
|
|
replacement.write_bytes(bank) |
|
|
|
|
|
|
|
original_open = os.open |
|
|
|
|
|
|
|
replaced = False |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def replace_then_open(value, flags, *args, **kwargs): |
|
|
|
|
|
|
|
nonlocal replaced |
|
|
|
|
|
|
|
if not replaced and Path(value) == candidate: |
|
|
|
|
|
|
|
replaced = True |
|
|
|
|
|
|
|
candidate.unlink() |
|
|
|
|
|
|
|
candidate.symlink_to(replacement) |
|
|
|
|
|
|
|
return original_open(value, flags, *args, **kwargs) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
monkeypatch.setattr(guided_imports, "os", os, raising=False) |
|
|
|
|
|
|
|
monkeypatch.setattr(guided_imports.os, "open", replace_then_open) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
with override_settings(IMPORT_ROOT=tmp_path): |
|
|
|
|
|
|
|
rows = discover_imports() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
assert [(row.name, row.profile, row.sections, row.code) for row in rows] == [ |
|
|
|
|
|
|
|
("candidate.csv", None, (), "UNREADABLE_FILE"), |
|
|
|
|
|
|
|
("replacement.csv", "BANK_CSV_V1", ("1",), None), |
|
|
|
|
|
|
|
] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_discovery_returns_empty_results_when_listing_the_import_root_fails(tmp_path, monkeypatch): |
|
|
|
|
|
|
|
root = tmp_path / "unavailable" |
|
|
|
|
|
|
|
root.mkdir() |
|
|
|
|
|
|
|
original_iterdir = Path.iterdir |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def delayed_failure(self): |
|
|
|
|
|
|
|
if self == root.resolve(): |
|
|
|
|
|
|
|
def entries(): |
|
|
|
|
|
|
|
raise OSError("SYNTHETIC LISTING FAILURE") |
|
|
|
|
|
|
|
yield |
|
|
|
|
|
|
|
return entries() |
|
|
|
|
|
|
|
return original_iterdir(self) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
monkeypatch.setattr(Path, "iterdir", delayed_failure) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
with override_settings(IMPORT_ROOT=root): |
|
|
|
|
|
|
|
assert discover_imports() == () |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_discovery_returns_empty_results_when_resolving_the_import_root_fails(tmp_path, monkeypatch): |
|
|
|
|
|
|
|
root = tmp_path / "unresolvable" |
|
|
|
|
|
|
|
original_resolve = Path.resolve |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def resolution_failure(self, *args, **kwargs): |
|
|
|
|
|
|
|
if self == root: |
|
|
|
|
|
|
|
raise RuntimeError("SYNTHETIC RESOLUTION FAILURE") |
|
|
|
|
|
|
|
return original_resolve(self, *args, **kwargs) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
monkeypatch.setattr(Path, "resolve", resolution_failure) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
with override_settings(IMPORT_ROOT=root): |
|
|
|
|
|
|
|
assert discover_imports() == () |
|
|
|
|