feat: complete production workflow migration
This commit is contained in:
@@ -8,8 +8,8 @@ from pathlib import Path, PurePosixPath
|
||||
from gyxx_flow.security.scanner import scan_repository
|
||||
|
||||
PROJECT_ROOT = Path(__file__).parents[1]
|
||||
RUNTIME_ROOT = (
|
||||
PROJECT_ROOT / "src" / "gyxx_flow" / "modules" / "content_marketing" / "runtime"
|
||||
MODULE_ROOT = (
|
||||
PROJECT_ROOT / "src" / "gyxx_flow" / "modules" / "content_marketing"
|
||||
)
|
||||
MANIFEST_PATH = (
|
||||
PROJECT_ROOT / "config" / "source-manifests" / "content_marketing.json"
|
||||
@@ -32,10 +32,8 @@ FIXED_USER_PATHS = (
|
||||
EXPECTED_CATEGORIES = {
|
||||
"config",
|
||||
"dependency",
|
||||
"launcher",
|
||||
"source",
|
||||
"sql",
|
||||
"test",
|
||||
"tool",
|
||||
}
|
||||
REQUIRED_SOURCES = {
|
||||
@@ -44,14 +42,13 @@ REQUIRED_SOURCES = {
|
||||
"pgy_xhs_scraper_v2.py",
|
||||
"xingtu_scraper_v2.py",
|
||||
"chanmama_scraper.py",
|
||||
"daily_creator_exposure_scope.py",
|
||||
"daily_marketing_report.py",
|
||||
"data/tools/daily_run.bat",
|
||||
"data/tools/friday_relogin_parallel.py",
|
||||
"data/tools/schema_gyxx_super_data.sql",
|
||||
"data/tools/migrations/005_style_product_profile.sql",
|
||||
"data/config/db.env.example",
|
||||
"data/config/款式_多维表格_对照.json",
|
||||
"tests/test_collection_completeness.py",
|
||||
}
|
||||
|
||||
|
||||
@@ -74,7 +71,7 @@ def test_content_source_snapshot_manifest_is_complete_and_well_formed() -> None:
|
||||
assert manifest["module"] == "content_marketing"
|
||||
files = manifest["files"]
|
||||
assert isinstance(files, list)
|
||||
assert len(files) == 90
|
||||
assert len(files) == 65
|
||||
|
||||
sources = [entry["source_relative_path"] for entry in files]
|
||||
targets = [entry["target_relative_path"] for entry in files]
|
||||
@@ -88,13 +85,13 @@ def test_content_source_snapshot_manifest_is_complete_and_well_formed() -> None:
|
||||
relative = PurePosixPath(entry["target_relative_path"])
|
||||
assert not relative.is_absolute()
|
||||
assert ".." not in relative.parts
|
||||
assert relative.parts[:5] == (
|
||||
assert relative.parts[:4] == (
|
||||
"src",
|
||||
"gyxx_flow",
|
||||
"modules",
|
||||
"content_marketing",
|
||||
"runtime",
|
||||
)
|
||||
assert relative.parts[4] != "runtime"
|
||||
assert SHA256.fullmatch(entry["source_sha256"])
|
||||
assert SHA256.fullmatch(entry["target_sha256"])
|
||||
assert entry["transformed"] is (
|
||||
@@ -110,7 +107,7 @@ def test_content_source_snapshot_targets_exist_and_match_manifest_hashes() -> No
|
||||
|
||||
generated = _manifest()["generated_files"]
|
||||
assert [entry["target_relative_path"] for entry in generated] == [
|
||||
"src/gyxx_flow/modules/content_marketing/runtime/runtime_paths.py"
|
||||
"src/gyxx_flow/modules/content_marketing/runtime_paths.py"
|
||||
]
|
||||
for entry in generated:
|
||||
target = PROJECT_ROOT.joinpath(*PurePosixPath(entry["target_relative_path"]).parts)
|
||||
@@ -133,6 +130,9 @@ def test_content_source_snapshot_exclusions_cover_runtime_and_sensitive_state()
|
||||
"data/notes/**",
|
||||
"data/reports/**",
|
||||
"data/tmp/**",
|
||||
"tests/**",
|
||||
"**/*.bat",
|
||||
"**/*.ps1",
|
||||
} <= excluded
|
||||
|
||||
|
||||
@@ -140,57 +140,51 @@ def test_content_source_snapshot_has_no_legacy_roots_or_plaintext_credentials()
|
||||
manifest_text = MANIFEST_PATH.read_text(encoding="utf-8").casefold()
|
||||
assert not any(root in manifest_text for root in OLD_PROJECT_ROOTS)
|
||||
|
||||
for path in RUNTIME_ROOT.rglob("*"):
|
||||
for path in MODULE_ROOT.rglob("*"):
|
||||
if not path.is_file() or b"\x00" in path.read_bytes():
|
||||
continue
|
||||
text = path.read_text(encoding="utf-8-sig", errors="strict").casefold()
|
||||
assert not any(root in text for root in OLD_PROJECT_ROOTS), path
|
||||
assert not any(root in text for root in FIXED_USER_PATHS), path
|
||||
|
||||
assert scan_repository(RUNTIME_ROOT) == []
|
||||
assert scan_repository(MODULE_ROOT) == []
|
||||
|
||||
|
||||
def test_chanmama_credentials_are_externalized_in_the_copied_source() -> None:
|
||||
source = (RUNTIME_ROOT / "chanmama_scraper.py").read_text(encoding="utf-8-sig")
|
||||
source = (MODULE_ROOT / "chanmama_scraper.py").read_text(encoding="utf-8-sig")
|
||||
|
||||
assert 'os.getenv("CHANMAMA_ACCOUNT", "")' in source
|
||||
assert 'os.getenv("CHANMAMA_PASSWORD", "")' in source
|
||||
assert not re.search(r'^PASSWORD\s*=\s*["\'][^"\']+["\']', source, re.MULTILINE)
|
||||
|
||||
|
||||
def test_content_runtime_has_one_portable_path_boundary() -> None:
|
||||
python_sources = list(RUNTIME_ROOT.rglob("*.py"))
|
||||
def test_content_module_has_one_portable_path_boundary() -> None:
|
||||
python_sources = list(MODULE_ROOT.rglob("*.py"))
|
||||
path_mutations: list[Path] = []
|
||||
forbidden_cross_module_imports: list[Path] = []
|
||||
|
||||
for path in python_sources:
|
||||
source = path.read_text(encoding="utf-8-sig")
|
||||
if re.search(r"sys\.path\.(?:insert|append)\(", source):
|
||||
path_mutations.append(path.relative_to(RUNTIME_ROOT))
|
||||
path_mutations.append(path.relative_to(MODULE_ROOT))
|
||||
if "lark_cli_runtime" in source:
|
||||
forbidden_cross_module_imports.append(path.relative_to(RUNTIME_ROOT))
|
||||
forbidden_cross_module_imports.append(path.relative_to(MODULE_ROOT))
|
||||
|
||||
assert path_mutations == []
|
||||
assert forbidden_cross_module_imports == []
|
||||
|
||||
runtime_paths = (
|
||||
RUNTIME_ROOT / "runtime_paths.py"
|
||||
MODULE_ROOT / "runtime_paths.py"
|
||||
).read_text(encoding="utf-8-sig")
|
||||
assert "GYXX_DATA_ROOT" in runtime_paths
|
||||
assert "GYXX_MODULE_ROOT" in runtime_paths
|
||||
|
||||
|
||||
def test_content_runtime_uses_portable_python_launchers_and_data_paths() -> None:
|
||||
batch_sources = list(RUNTIME_ROOT.rglob("*.bat"))
|
||||
assert batch_sources
|
||||
def test_content_module_uses_python_commands_and_portable_data_paths() -> None:
|
||||
assert list(MODULE_ROOT.rglob("*.bat")) == []
|
||||
assert list(MODULE_ROOT.rglob("*.ps1")) == []
|
||||
|
||||
for path in batch_sources:
|
||||
source = path.read_text(encoding="utf-8-sig").casefold()
|
||||
assert "%gyxx_python%" in source, path
|
||||
assert "hermes-agent" not in source, path
|
||||
assert "appdata" not in source, path
|
||||
|
||||
path_constants = (RUNTIME_ROOT / "runtime_paths.py").read_text(
|
||||
path_constants = (MODULE_ROOT / "runtime_paths.py").read_text(
|
||||
encoding="utf-8-sig"
|
||||
)
|
||||
assert 'DataLayout(data_root).for_module("content_marketing")' in path_constants
|
||||
|
||||
Reference in New Issue
Block a user