import hashlib import json import os import subprocess import sys import tempfile import unittest from pathlib import Path REPOSITORY_ROOT = Path(__file__).resolve().parents[1] SCRIPT = REPOSITORY_ROOT / "scripts" / "build-d3-local-frontend-image.sh" sys.path.insert(0, str(REPOSITORY_ROOT / "scripts")) from lib.final_pack_contract import metadata_tree_sha256 # noqa: E402 from materialize_frontend_app_packs_source import ( # noqa: E402 MaterializeError, materialize_frontend_app_packs_source, ) class LocalFrontendBuilderBoundaryTests(unittest.TestCase): def setUp(self) -> None: self.temporary_directory = tempfile.TemporaryDirectory() self.root = Path(self.temporary_directory.name).resolve() self.core_root = self.root / "core" self.app_packs_root = self.root / "app-packs" self.l2_root = self.root / "l2" self.capture_root = self.root / "capture" self.fake_bin = self.root / "bin" self.capture_root.mkdir() self.fake_bin.mkdir() self._write(self.core_root / "frontend" / "package-lock.json", "{}\n") self._write(self.app_packs_root / "README.md", "app packs\n") self._write( self.l2_root / "stack.lock.json", json.dumps({"packs": ["packs/Test"]}) + "\n", ) self._write( self.l2_root / "packs" / "Test" / "pack.json", json.dumps({"name": "Test"}) + "\n", ) self._write( self.l2_root / "packs" / "Test" / "frontend" / "source-manifest.json", "{}\n", ) self._write(self.l2_root / "metadata" / "fixture.json", "{}\n") self.core_commit = self._commit(self.core_root) self.app_packs_commit = self._commit(self.app_packs_root) self.l2_commit = self._commit(self.l2_root) self.metadata_digest = metadata_tree_sha256(self.l2_root / "metadata") self.docker_marker = self.capture_root / "docker-called" self._write_executable( self.fake_bin / "docker", f"""#!{sys.executable} import pathlib import sys if sys.argv[1:] == ["buildx", "version"]: raise SystemExit(0) pathlib.Path({str(self.docker_marker)!r}).write_text(" ".join(sys.argv[1:])) raise SystemExit(73) """, ) self._write_executable( self.fake_bin / "npm", f"""#!{sys.executable} import json import os import pathlib import sys name = "ci" if sys.argv[1:2] == ["ci"] else "build" pathlib.Path({str(self.capture_root)!r}, name + ".json").write_text( json.dumps({{ "names": sorted(os.environ), "app_packs_root": os.environ.get("NEXUSCRM_FRONTEND_APP_PACKS_ROOT"), "app_packs_ref": os.environ.get("NEXUSCRM_FRONTEND_APP_PACKS_REF"), }}, sort_keys=True) ) if name == "build": dist = pathlib.Path.cwd() / "dist" dist.mkdir() (dist / "index.html").write_text("\\n") (dist / "version.json").write_text("{{}}\\n") """, ) self._write_executable( self.fake_bin / "mktemp", f"""#!{sys.executable} import tempfile print(tempfile.mkdtemp(dir={str(self.root)!r})) """, ) def tearDown(self) -> None: self.temporary_directory.cleanup() @staticmethod def _write(path: Path, content: str) -> None: path.parent.mkdir(parents=True, exist_ok=True) path.write_text(content) @staticmethod def _write_executable(path: Path, content: str) -> None: path.write_text(content) path.chmod(0o755) @staticmethod def _commit(root: Path) -> str: subprocess.run(["git", "init", "-q", str(root)], check=True) subprocess.run( ["git", "-C", str(root), "config", "user.name", "Test"], check=True ) subprocess.run( ["git", "-C", str(root), "config", "user.email", "test@example.invalid"], check=True, ) subprocess.run(["git", "-C", str(root), "add", "--all"], check=True) subprocess.run( ["git", "-C", str(root), "commit", "-q", "-m", "fixture"], check=True, ) return subprocess.check_output( ["git", "-C", str(root), "rev-parse", "HEAD"], text=True ).strip() def _run(self, output_manifest: Path) -> subprocess.CompletedProcess[str]: test_home = self.root / "home" test_home.mkdir() environment = { "PATH": f"{self.fake_bin}{os.pathsep}{os.environ['PATH']}", "HOME": str(test_home), "TMPDIR": str(self.root), "LANG": "C", "LC_ALL": "C", "BUILD_LEAK_SENTINEL": "must-not-reach-npm", "NODE_OPTIONS": "--trace-warnings", "REACT_APP_SALESFORCE_CLIENT_SECRET": "must-not-be-compiled", "VITE_UNDECLARED_REMOTE": "https://example.invalid", } return subprocess.run( [ "bash", str(SCRIPT), "--core-root", str(self.core_root), "--app-packs-root", str(self.app_packs_root), "--l2-root", str(self.l2_root), "--core-commit", self.core_commit, "--app-packs-commit", self.app_packs_commit, "--l2-commit", self.l2_commit, "--l2-metadata-path", "metadata", "--l2-metadata-tree-sha256", self.metadata_digest, "--tag-prefix", "nexuscrm/d3-local", "--output-manifest", str(output_manifest), ], cwd=REPOSITORY_ROOT, env=environment, text=True, capture_output=True, check=False, ) def test_existing_directory_manifest_fails_before_build(self) -> None: output_directory = self.root / "manifest" output_directory.mkdir() result = self._run(output_directory) self.assertNotEqual(result.returncode, 0) self.assertIn("--output-manifest must name a file", result.stderr) self.assertFalse((self.capture_root / "ci.json").exists()) self.assertFalse(self.docker_marker.exists()) def test_ignored_checkout_input_fails_before_materialization(self) -> None: self._write(self.l2_root / ".gitignore", "*.local\n") subprocess.run( ["git", "-C", str(self.l2_root), "add", ".gitignore"], check=True ) subprocess.run( ["git", "-C", str(self.l2_root), "commit", "-q", "-m", "ignore"], check=True, ) self.l2_commit = subprocess.check_output( ["git", "-C", str(self.l2_root), "rev-parse", "HEAD"], text=True ).strip() self._write( self.l2_root / "packs" / "Test" / "frontend" / ".env.local", "VITE_REMOTE=https://undeclared.invalid\n", ) result = self._run(self.root / "manifest.json") self.assertNotEqual(result.returncode, 0) self.assertIn( "L2 checkout must contain only tracked HEAD inputs", result.stderr ) self.assertFalse((self.capture_root / "ci.json").exists()) def test_branch_javascript_runs_only_in_trusted_buildkit_stage(self) -> None: result = self._run(self.root / "manifest.json") self.assertEqual(73, result.returncode, result.stdout + result.stderr) self.assertTrue(self.docker_marker.exists(), result.stdout + result.stderr) docker_command = self.docker_marker.read_text() self.assertIn("buildx build", docker_command) self.assertIn("--target frontend-dist", docker_command) self.assertIn("serviceification-frontend-branch-build.Dockerfile", docker_command) for forbidden in ( "BUILD_LEAK_SENTINEL", "NODE_OPTIONS", "REACT_APP_SALESFORCE_CLIENT_SECRET", "VITE_UNDECLARED_REMOTE", ): self.assertNotIn(forbidden, docker_command) self.assertFalse((self.capture_root / "ci.json").exists()) self.assertFalse((self.capture_root / "build.json").exists()) dockerfile = ( REPOSITORY_ROOT / "infra" / "serviceification-frontend-branch-build.Dockerfile" ).read_text() self.assertRegex(dockerfile, r"FROM node:24-bookworm-slim@sha256:[0-9a-f]{64}") self.assertIn("npm ci --include=dev --ignore-scripts", dockerfile) self.assertIn( "npm run provision:frontend-app-packages --ignore-scripts", dockerfile, ) self.assertIn("npm run build --ignore-scripts", dockerfile) self.assertEqual(2, dockerfile.count("RUN --network=none env")) for proxy_name in ( "HTTP_PROXY", "HTTPS_PROXY", "FTP_PROXY", "ALL_PROXY", "NO_PROXY", "http_proxy", "https_proxy", "ftp_proxy", "all_proxy", "no_proxy", ): self.assertIn(f"{proxy_name}=", dockerfile) self.assertEqual(2, dockerfile.count(f"-u {proxy_name}")) def test_buildkit_export_symlink_is_rejected_before_host_read_or_write(self) -> None: outside = self.root / "outside-version.json" outside.write_text("host bytes\n") self._write_executable( self.fake_bin / "docker", f"""#!{sys.executable} import pathlib import sys if sys.argv[1:] == ["buildx", "version"]: raise SystemExit(0) arguments = sys.argv[1:] output = arguments[arguments.index("--output") + 1] destination = pathlib.Path(output.removeprefix("type=local,dest=")) destination.mkdir(parents=True) (destination / "index.html").write_text("\\n") (destination / "version.json").symlink_to(pathlib.Path({str(outside)!r})) raise SystemExit(0) """, ) result = self._run(self.root / "manifest.json") self.assertNotEqual(result.returncode, 0, result.stdout + result.stderr) self.assertIn("frontend BuildKit export contains a symlink or special entry", result.stderr) self.assertEqual("host bytes\n", outside.read_text()) def test_final_packaging_refreshes_generated_source_bindings(self) -> None: old_commit_environment = dict(os.environ) old_commit_environment.update({ "GIT_AUTHOR_DATE": "2001-01-01T00:00:00Z", "GIT_COMMITTER_DATE": "2001-01-01T00:00:00Z", }) subprocess.run( ["git", "-C", str(self.l2_root), "commit", "--amend", "--no-edit", "-q"], check=True, env=old_commit_environment, ) self.l2_commit = subprocess.check_output( ["git", "-C", str(self.l2_root), "rev-parse", "HEAD"], text=True, ).strip() self._write_executable( self.fake_bin / "docker", f"""#!{sys.executable} import json import pathlib import sys arguments = sys.argv[1:] if arguments == ["buildx", "version"]: raise SystemExit(0) if "--output" in arguments: if "--no-cache" in arguments: raise SystemExit("expensive JavaScript cache was disabled") output = arguments[arguments.index("--output") + 1] destination = pathlib.Path(output.removeprefix("type=local,dest=")) destination.mkdir(parents=True) (destination / "index.html").write_text("\\n") (destination / "version.json").write_text("{{}}\\n") raise SystemExit(0) context = pathlib.Path(arguments[-1]) source = context / "source-tuple.json" version = context / "frontend-dist" / "version.json" pathlib.Path({str(self.capture_root / "packaging.json")!r}).write_text(json.dumps({{ "arguments": arguments, "source": source.read_text(), "version": version.read_text(), "source_mtime": source.stat().st_mtime, "version_mtime": version.stat().st_mtime, }})) raise SystemExit(73) """, ) result = self._run(self.root / "manifest.json") self.assertEqual(73, result.returncode, result.stdout + result.stderr) packaging = json.loads((self.capture_root / "packaging.json").read_text()) self.assertIn("--no-cache", packaging["arguments"]) self.assertIn("--load", packaging["arguments"]) self.assertNotIn("--push", packaging["arguments"]) self.assertGreater(packaging["source_mtime"], 978307200) self.assertGreater(packaging["version_mtime"], 978307200) source = json.loads(packaging["source"]) self.assertEqual(self.core_commit, source["nexuscrm_commit"]) self.assertEqual(self.app_packs_commit, source["app_packs_commit"]) self.assertEqual(self.l2_commit, source["l2_commit"]) expected_binding = "sha256:" + hashlib.sha256(packaging["source"].encode()).hexdigest() self.assertEqual(expected_binding, json.loads(packaging["version"])["version"]) self.assertIn("SOURCE_TUPLE_SHA256=" + expected_binding, packaging["arguments"]) self.assertFalse((self.root / "manifest.json").exists()) def test_tracked_metadata_symlink_is_rejected_before_hashing(self) -> None: outside = self.root / "outside-metadata.json" outside.write_text("must not be hashed\n") fixture = self.l2_root / "metadata" / "fixture.json" fixture.unlink() fixture.symlink_to(outside) subprocess.run(["git", "-C", str(self.l2_root), "add", str(fixture)], check=True) subprocess.run( ["git", "-C", str(self.l2_root), "commit", "-q", "-m", "metadata symlink"], check=True, ) self.l2_commit = subprocess.check_output( ["git", "-C", str(self.l2_root), "rev-parse", "HEAD"], text=True ).strip() result = self._run(self.root / "manifest.json") self.assertNotEqual(result.returncode, 0) self.assertIn("L2 input tree contains a tracked symlink", result.stderr) self.assertFalse(self.docker_marker.exists()) def test_unowned_empty_metadata_directory_is_rejected_before_hashing(self) -> None: fixture = self.l2_root / "metadata" / "fixture.json" subprocess.run( ["git", "-C", str(self.l2_root), "rm", "-q", str(fixture)], check=True, ) subprocess.run( ["git", "-C", str(self.l2_root), "commit", "-q", "-m", "delete metadata"], check=True, ) metadata_root = self.l2_root / "metadata" metadata_root.mkdir() self.l2_commit = subprocess.check_output( ["git", "-C", str(self.l2_root), "rev-parse", "HEAD"], text=True ).strip() self.metadata_digest = metadata_tree_sha256(metadata_root) result = self._run(self.root / "manifest.json") self.assertNotEqual(result.returncode, 0) self.assertIn("L2 input tree contains", result.stderr) self.assertFalse(self.docker_marker.exists()) def test_tracked_symlink_is_rejected_before_materialization_or_build(self) -> None: outside = self.root / "host-secret" outside.write_text("must not be copied\n") link = self.l2_root / "packs" / "Test" / "frontend" / "host-secret" link.symlink_to(outside) subprocess.run(["git", "-C", str(self.l2_root), "add", str(link)], check=True) subprocess.run( ["git", "-C", str(self.l2_root), "commit", "-q", "-m", "symlink"], check=True, ) self.l2_commit = subprocess.check_output( ["git", "-C", str(self.l2_root), "rev-parse", "HEAD"], text=True ).strip() result = self._run(self.root / "manifest.json") self.assertNotEqual(result.returncode, 0) self.assertIn("symlink or special entry", result.stderr) self.assertFalse(self.docker_marker.exists()) with self.assertRaisesRegex(MaterializeError, "symlink or special entry"): materialize_frontend_app_packs_source( downstream_root=self.l2_root, official_app_packs_root=self.app_packs_root, target_root=self.root / "materialized", ) if __name__ == "__main__": unittest.main()