chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:47:58 +08:00
commit b16403ea71
789 changed files with 115226 additions and 0 deletions
@@ -0,0 +1,334 @@
import os
import tempfile
import pytest
from e2b.template.utils import get_all_files_in_path
class TestGetAllFilesInPath:
@pytest.fixture
def test_dir(self):
"""Create a temporary directory for testing."""
with tempfile.TemporaryDirectory() as tmpdir:
yield tmpdir
def test_should_return_files_matching_simple_pattern(self, test_dir):
"""Test that function returns files matching a simple pattern."""
# Create test files
with open(os.path.join(test_dir, "file1.txt"), "w") as f:
f.write("content1")
with open(os.path.join(test_dir, "file2.txt"), "w") as f:
f.write("content2")
with open(os.path.join(test_dir, "file3.js"), "w") as f:
f.write("content3")
files = get_all_files_in_path("*.txt", test_dir, [])
assert len(files) == 2
assert any("file1.txt" in f for f in files)
assert any("file2.txt" in f for f in files)
assert not any("file3.js" in f for f in files)
def test_should_handle_directory_patterns_recursively(self, test_dir):
"""Test that function handles directory patterns recursively."""
# Create nested directory structure
os.makedirs(os.path.join(test_dir, "src", "components"), exist_ok=True)
os.makedirs(os.path.join(test_dir, "src", "utils"), exist_ok=True)
with open(os.path.join(test_dir, "src", "index.ts"), "w") as f:
f.write("index content")
with open(os.path.join(test_dir, "src", "components", "Button.tsx"), "w") as f:
f.write("button content")
with open(os.path.join(test_dir, "src", "utils", "helper.ts"), "w") as f:
f.write("helper content")
with open(os.path.join(test_dir, "README.md"), "w") as f:
f.write("readme content")
files = get_all_files_in_path("src", test_dir, [])
assert len(files) == 6 # 3 files + 3 directories (src, components, utils)
assert any("index.ts" in f for f in files)
assert any("Button.tsx" in f for f in files)
assert any("helper.ts" in f for f in files)
assert not any("README.md" in f for f in files)
def test_should_respect_ignore_patterns(self, test_dir):
"""Test that function respects ignore patterns."""
# Create test files
with open(os.path.join(test_dir, "file1.txt"), "w") as f:
f.write("content1")
with open(os.path.join(test_dir, "file2.txt"), "w") as f:
f.write("content2")
with open(os.path.join(test_dir, "temp.txt"), "w") as f:
f.write("temp content")
with open(os.path.join(test_dir, "backup.txt"), "w") as f:
f.write("backup content")
files = get_all_files_in_path("*.txt", test_dir, ["temp*", "backup*"])
assert len(files) == 2
assert any("file1.txt" in f for f in files)
assert any("file2.txt" in f for f in files)
assert not any("temp.txt" in f for f in files)
assert not any("backup.txt" in f for f in files)
def test_should_handle_complex_ignore_patterns(self, test_dir):
"""Test that function handles complex ignore patterns."""
# Create nested structure with various file types
os.makedirs(os.path.join(test_dir, "src", "components"), exist_ok=True)
os.makedirs(os.path.join(test_dir, "src", "utils"), exist_ok=True)
os.makedirs(os.path.join(test_dir, "tests"), exist_ok=True)
with open(os.path.join(test_dir, "src", "index.ts"), "w") as f:
f.write("index content")
with open(os.path.join(test_dir, "src", "components", "Button.tsx"), "w") as f:
f.write("button content")
with open(os.path.join(test_dir, "src", "utils", "helper.ts"), "w") as f:
f.write("helper content")
with open(os.path.join(test_dir, "tests", "test.spec.ts"), "w") as f:
f.write("test content")
with open(
os.path.join(test_dir, "src", "components", "Button.test.tsx"), "w"
) as f:
f.write("test content")
with open(os.path.join(test_dir, "src", "utils", "helper.spec.ts"), "w") as f:
f.write("spec content")
files = get_all_files_in_path("src", test_dir, ["**/*.test.*", "**/*.spec.*"])
assert len(files) == 6 # 3 files + 3 directories (src, components, utils)
assert any("index.ts" in f for f in files)
assert any("Button.tsx" in f for f in files)
assert any("helper.ts" in f for f in files)
assert not any("Button.test.tsx" in f for f in files)
assert not any("helper.spec.ts" in f for f in files)
def test_should_handle_empty_directories(self, test_dir):
"""Test that function handles empty directories."""
os.makedirs(os.path.join(test_dir, "empty"), exist_ok=True)
with open(os.path.join(test_dir, "file.txt"), "w") as f:
f.write("content")
files = get_all_files_in_path("empty", test_dir, [])
assert len(files) == 1
def test_should_handle_mixed_files_and_directories(self, test_dir):
"""Test that function handles mixed files and directories."""
# Create a mix of files and directories
with open(os.path.join(test_dir, "file1.txt"), "w") as f:
f.write("content1")
os.makedirs(os.path.join(test_dir, "dir1"), exist_ok=True)
with open(os.path.join(test_dir, "dir1", "file2.txt"), "w") as f:
f.write("content2")
with open(os.path.join(test_dir, "file3.txt"), "w") as f:
f.write("content3")
files = get_all_files_in_path("*", test_dir, [])
assert len(files) == 4
assert any("file1.txt" in f for f in files)
assert any("file2.txt" in f for f in files)
assert any("file3.txt" in f for f in files)
def test_should_handle_glob_patterns_with_subdirectories(self, test_dir):
"""Test that function handles glob patterns with subdirectories."""
# Create nested structure
os.makedirs(os.path.join(test_dir, "src", "components"), exist_ok=True)
os.makedirs(os.path.join(test_dir, "src", "utils"), exist_ok=True)
with open(os.path.join(test_dir, "src", "index.ts"), "w") as f:
f.write("index content")
with open(os.path.join(test_dir, "src", "components", "Button.tsx"), "w") as f:
f.write("button content")
with open(os.path.join(test_dir, "src", "utils", "helper.ts"), "w") as f:
f.write("helper content")
with open(os.path.join(test_dir, "src", "components", "Button.css"), "w") as f:
f.write("css content")
files = get_all_files_in_path("src/**/*", test_dir, [])
assert len(files) == 6
assert any("index.ts" in f for f in files)
assert any("Button.tsx" in f for f in files)
assert any("helper.ts" in f for f in files)
assert any("Button.css" in f for f in files)
def test_should_handle_specific_file_extensions(self, test_dir):
"""Test that function handles specific file extensions."""
with open(os.path.join(test_dir, "file1.ts"), "w") as f:
f.write("ts content")
with open(os.path.join(test_dir, "file2.js"), "w") as f:
f.write("js content")
with open(os.path.join(test_dir, "file3.tsx"), "w") as f:
f.write("tsx content")
with open(os.path.join(test_dir, "file4.css"), "w") as f:
f.write("css content")
files = get_all_files_in_path("*.ts", test_dir, [])
assert len(files) == 1
assert any("file1.ts" in f for f in files)
def test_should_return_sorted_files(self, test_dir):
"""Test that function returns sorted files."""
with open(os.path.join(test_dir, "zebra.txt"), "w") as f:
f.write("z content")
with open(os.path.join(test_dir, "apple.txt"), "w") as f:
f.write("a content")
with open(os.path.join(test_dir, "banana.txt"), "w") as f:
f.write("b content")
files = get_all_files_in_path("*.txt", test_dir, [])
assert len(files) == 3
assert "apple.txt" in files[0]
assert "banana.txt" in files[1]
assert "zebra.txt" in files[2]
def test_should_handle_no_matching_files(self, test_dir):
"""Test that function handles no matching files."""
with open(os.path.join(test_dir, "file.txt"), "w") as f:
f.write("content")
files = get_all_files_in_path("*.js", test_dir, [])
assert len(files) == 0
def test_should_handle_complex_ignore_patterns_with_directories(self, test_dir):
"""Test that function handles complex ignore patterns with directories."""
# Create a complex structure
os.makedirs(os.path.join(test_dir, "src", "components"), exist_ok=True)
os.makedirs(os.path.join(test_dir, "src", "utils"), exist_ok=True)
os.makedirs(os.path.join(test_dir, "src", "tests"), exist_ok=True)
os.makedirs(os.path.join(test_dir, "dist"), exist_ok=True)
with open(os.path.join(test_dir, "src", "index.ts"), "w") as f:
f.write("index content")
with open(os.path.join(test_dir, "src", "components", "Button.tsx"), "w") as f:
f.write("button content")
with open(os.path.join(test_dir, "src", "utils", "helper.ts"), "w") as f:
f.write("helper content")
with open(os.path.join(test_dir, "src", "tests", "test.spec.ts"), "w") as f:
f.write("test content")
with open(os.path.join(test_dir, "dist", "bundle.js"), "w") as f:
f.write("bundle content")
with open(os.path.join(test_dir, "README.md"), "w") as f:
f.write("readme content")
files = get_all_files_in_path("src", test_dir, ["**/tests/**", "**/*.spec.*"])
assert len(files) == 6 # 3 files + 3 directories (src, components, utils)
assert any("index.ts" in f for f in files)
assert any("Button.tsx" in f for f in files)
assert any("helper.ts" in f for f in files)
assert not any("test.spec.ts" in f for f in files)
def test_should_include_dotfiles(self, test_dir):
"""Test that function includes files starting with a dot."""
with open(os.path.join(test_dir, "file.txt"), "w") as f:
f.write("content")
with open(os.path.join(test_dir, ".env"), "w") as f:
f.write("SECRET=123")
with open(os.path.join(test_dir, ".gitignore"), "w") as f:
f.write("node_modules")
files = get_all_files_in_path("*", test_dir, [])
assert len(files) == 3
assert any(".env" in f for f in files)
assert any(".gitignore" in f for f in files)
assert any("file.txt" in f for f in files)
def test_should_include_dotfiles_in_subdirectories(self, test_dir):
"""Test that function includes dotfiles inside subdirectories."""
os.makedirs(os.path.join(test_dir, "src"), exist_ok=True)
with open(os.path.join(test_dir, "src", "index.ts"), "w") as f:
f.write("content")
with open(os.path.join(test_dir, "src", ".env.local"), "w") as f:
f.write("SECRET=123")
files = get_all_files_in_path("src", test_dir, [])
assert any("index.ts" in f for f in files)
assert any(".env.local" in f for f in files)
def test_should_include_dotdirectories_and_their_contents(self, test_dir):
"""Test that function includes dot-prefixed directories and their contents."""
os.makedirs(os.path.join(test_dir, ".hidden"), exist_ok=True)
with open(os.path.join(test_dir, ".hidden", "config.json"), "w") as f:
f.write("{}")
with open(os.path.join(test_dir, "visible.txt"), "w") as f:
f.write("content")
files = get_all_files_in_path("*", test_dir, [])
assert any(".hidden" in f for f in files)
assert any("config.json" in f for f in files)
assert any("visible.txt" in f for f in files)
def test_should_respect_ignore_patterns_for_dotfiles(self, test_dir):
"""Test that dotfiles can be excluded via ignore patterns."""
with open(os.path.join(test_dir, ".env"), "w") as f:
f.write("SECRET=123")
with open(os.path.join(test_dir, ".gitignore"), "w") as f:
f.write("node_modules")
with open(os.path.join(test_dir, "file.txt"), "w") as f:
f.write("content")
files = get_all_files_in_path("*", test_dir, [".env"])
assert len(files) == 2
assert not any(f.endswith(".env") for f in files)
assert any(".gitignore" in f for f in files)
assert any("file.txt" in f for f in files)
def test_should_handle_symlinks(self, test_dir):
"""Test that function handles symbolic links."""
# Create a file and a symlink to it
with open(os.path.join(test_dir, "original.txt"), "w") as f:
f.write("original content")
# Create symlink (only on Unix-like systems)
if hasattr(os, "symlink"):
os.symlink("original.txt", os.path.join(test_dir, "link.txt"))
files = get_all_files_in_path("*.txt", test_dir, [])
assert len(files) == 2
assert any("original.txt" in f for f in files)
assert any("link.txt" in f for f in files)
def test_should_handle_nested_ignore_patterns(self, test_dir):
"""Test that function handles nested ignore patterns."""
# Create nested structure
os.makedirs(os.path.join(test_dir, "src", "components", "ui"), exist_ok=True)
os.makedirs(os.path.join(test_dir, "src", "components", "forms"), exist_ok=True)
os.makedirs(os.path.join(test_dir, "src", "utils"), exist_ok=True)
with open(os.path.join(test_dir, "src", "index.ts"), "w") as f:
f.write("index content")
with open(
os.path.join(test_dir, "src", "components", "ui", "Button.tsx"), "w"
) as f:
f.write("button content")
with open(
os.path.join(test_dir, "src", "components", "forms", "Input.tsx"), "w"
) as f:
f.write("input content")
with open(os.path.join(test_dir, "src", "utils", "helper.ts"), "w") as f:
f.write("helper content")
with open(
os.path.join(test_dir, "src", "components", "ui", "Button.test.tsx"), "w"
) as f:
f.write("test content")
files = get_all_files_in_path("src", test_dir, ["**/ui/**"])
assert (
len(files) == 7
) # 3 files + 4 directories (src, components, forms, utils)
assert any("index.ts" in f for f in files)
assert any("Input.tsx" in f for f in files)
assert any("helper.ts" in f for f in files)
assert not any("Button.tsx" in f for f in files)
assert not any("Button.test.tsx" in f for f in files)
@@ -0,0 +1,6 @@
import os
from e2b.template.utils import get_caller_directory
def test_get_caller_directory():
assert get_caller_directory(1) == os.path.dirname(__file__)
@@ -0,0 +1,45 @@
import pytest
from e2b.template.utils import normalize_build_arguments
from e2b.exceptions import TemplateException
def test_handles_string_name():
result = normalize_build_arguments(name="my-template:v1.0")
assert result == "my-template:v1.0"
def test_handles_name_without_tag():
result = normalize_build_arguments(name="my-template")
assert result == "my-template"
def test_handles_legacy_alias():
result = normalize_build_arguments(alias="my-template")
assert result == "my-template"
def test_name_takes_precedence_over_alias():
# When both are provided, name should be used
result = normalize_build_arguments(name="from-name", alias="from-alias")
assert result == "from-name"
def test_throws_for_empty_name():
with pytest.raises(TemplateException, match="Name must be provided"):
normalize_build_arguments(name="")
def test_throws_for_empty_alias():
with pytest.raises(TemplateException, match="Name must be provided"):
normalize_build_arguments(alias="")
def test_throws_for_missing_name_and_alias():
with pytest.raises(TemplateException, match="Name must be provided"):
normalize_build_arguments()
def test_throws_for_none_values():
with pytest.raises(TemplateException, match="Name must be provided"):
normalize_build_arguments(name=None, alias=None)
@@ -0,0 +1,29 @@
from e2b.template.utils import strip_ansi_escape_codes
def test_strips_basic_sgr_color():
assert strip_ansi_escape_codes("\x1b[31mred\x1b[0m") == "red"
def test_strips_semicolon_separated_params():
assert strip_ansi_escape_codes("\x1b[1;31;42mhi\x1b[0m") == "hi"
def test_strips_semicolon_256_color():
assert strip_ansi_escape_codes("\x1b[38;5;82mX\x1b[0m") == "X"
def test_strips_colon_256_color():
assert strip_ansi_escape_codes("\x1b[38:5:82mX\x1b[0m") == "X"
def test_strips_colon_truecolor():
assert strip_ansi_escape_codes("\x1b[38:2::255:0:0mRED\x1b[0m") == "RED"
def test_strips_colon_curly_underline():
assert strip_ansi_escape_codes("\x1b[4:3mX\x1b[0m") == "X"
def test_leaves_plain_text_unchanged():
assert strip_ansi_escape_codes("no escape codes here") == "no escape codes here"
@@ -0,0 +1,172 @@
import os
import tempfile
import tarfile
import pytest
from typing import IO
from e2b.template.utils import tar_file_stream
class TestTarFileStream:
@pytest.fixture
def test_dir(self):
"""Create a temporary directory for testing."""
tmpdir = tempfile.TemporaryDirectory()
yield tmpdir.name
tmpdir.cleanup()
def _extract_tar_contents(self, tar_buffer: IO[bytes]) -> dict:
"""Extract tar contents into a dictionary mapping paths to file contents."""
tar_buffer.seek(0)
contents = {}
# "r:*" auto-detects compressed vs. uncompressed archives
with tarfile.open(fileobj=tar_buffer, mode="r:*") as tar:
for member in tar.getmembers():
if member.isfile():
file_obj = tar.extractfile(member)
if file_obj:
contents[member.name] = file_obj.read()
elif member.isdir():
contents[member.name] = None # Mark as directory
return contents
def test_should_create_tar_with_simple_files(self, test_dir):
"""Test that function creates tar with simple files."""
# Create test files
file1_path = os.path.join(test_dir, "file1.txt")
file2_path = os.path.join(test_dir, "file2.txt")
with open(file1_path, "w") as f:
f.write("content1")
with open(file2_path, "w") as f:
f.write("content2")
tar_buffer = tar_file_stream("*.txt", test_dir, [], False, True)
contents = self._extract_tar_contents(tar_buffer)
assert len(contents) == 2
assert "file1.txt" in contents
assert "file2.txt" in contents
assert contents["file1.txt"] == b"content1"
assert contents["file2.txt"] == b"content2"
def test_should_create_uncompressed_tar_when_gzip_disabled(self, test_dir):
"""Test that function creates an uncompressed tar when gzip=False."""
file1_path = os.path.join(test_dir, "file1.txt")
file2_path = os.path.join(test_dir, "file2.txt")
with open(file1_path, "w") as f:
f.write("content1")
with open(file2_path, "w") as f:
f.write("content2")
tar_buffer = tar_file_stream("*.txt", test_dir, [], False, False)
# gzip streams start with the magic bytes 0x1f 0x8b — a plain tar must not
tar_buffer.seek(0)
assert tar_buffer.read(2) != b"\x1f\x8b"
# The archive must still be readable and contain the original files
contents = self._extract_tar_contents(tar_buffer)
assert contents["file1.txt"] == b"content1"
assert contents["file2.txt"] == b"content2"
def test_should_respect_ignore_patterns(self, test_dir):
"""Test that function respects ignore patterns."""
# Create test files
with open(os.path.join(test_dir, "file1.txt"), "w") as f:
f.write("content1")
with open(os.path.join(test_dir, "file2.txt"), "w") as f:
f.write("content2")
with open(os.path.join(test_dir, "temp.txt"), "w") as f:
f.write("temp content")
with open(os.path.join(test_dir, "backup.txt"), "w") as f:
f.write("backup content")
tar_buffer = tar_file_stream(
"*.txt", test_dir, ["temp*", "backup*"], False, True
)
contents = self._extract_tar_contents(tar_buffer)
assert len(contents) == 2
assert "file1.txt" in contents
assert "file2.txt" in contents
assert contents["file1.txt"] == b"content1"
assert contents["file2.txt"] == b"content2"
assert "temp.txt" not in contents
assert "backup.txt" not in contents
def test_should_handle_nested_files(self, test_dir):
"""Test that function handles nested directory structures."""
# Create nested directory structure
nested_dir = os.path.join(test_dir, "src", "components")
os.makedirs(nested_dir, exist_ok=True)
with open(os.path.join(test_dir, "src", "index.ts"), "w") as f:
f.write("index content")
with open(os.path.join(nested_dir, "Button.tsx"), "w") as f:
f.write("button content")
tar_buffer = tar_file_stream("src", test_dir, [], False, True)
contents = self._extract_tar_contents(tar_buffer)
# Should include the directory and files
assert "src/index.ts" in contents
assert "src/components/Button.tsx" in contents
def test_should_resolve_symlinks_when_enabled(self, test_dir):
"""Test that function resolves symlinks when resolve_symlinks=True."""
if not hasattr(os, "symlink"):
pytest.skip("Symlinks not supported on this platform")
# Create original file
original_path = os.path.join(test_dir, "original.txt")
with open(original_path, "w") as f:
f.write("original content")
# Create symlink
symlink_path = os.path.join(test_dir, "link.txt")
os.symlink("original.txt", symlink_path)
# Test with resolve_symlinks=True
tar_buffer = tar_file_stream("*.txt", test_dir, [], True, True)
contents = self._extract_tar_contents(tar_buffer)
# Both files should be in tar
assert "original.txt" in contents
assert "link.txt" in contents
# Symlink should be resolved (contain actual content, not link)
assert contents["original.txt"] == b"original content"
assert contents["link.txt"] == b"original content"
def test_should_preserve_symlinks_when_disabled(self, test_dir):
"""Test that function preserves symlinks when resolve_symlinks=False."""
if not hasattr(os, "symlink"):
pytest.skip("Symlinks not supported on this platform")
# Create original file
original_path = os.path.join(test_dir, "original.txt")
with open(original_path, "w") as f:
f.write("original content")
# Create symlink
symlink_path = os.path.join(test_dir, "link.txt")
os.symlink("original.txt", symlink_path)
# Test with resolve_symlinks=False
tar_buffer = tar_file_stream("*.txt", test_dir, [], False, True)
tar_buffer.seek(0)
with tarfile.open(fileobj=tar_buffer, mode="r:gz") as tar:
members = {m.name: m for m in tar.getmembers()}
# Both files should be in tar
assert "original.txt" in members
assert "link.txt" in members
# Original should be a regular file
assert members["original.txt"].isfile()
assert not members["original.txt"].issym()
# Link should be a symlink
assert members["link.txt"].issym()
assert members["link.txt"].linkname == "original.txt"
@@ -0,0 +1,106 @@
import sys
import pytest
from e2b.template.utils import validate_relative_path
from e2b.exceptions import TemplateException
is_windows = sys.platform == "win32"
class TestValidateRelativePathValid:
"""Test cases for valid paths."""
def test_accepts_simple_relative_path(self):
validate_relative_path("foo", None)
def test_accepts_nested_relative_path(self):
validate_relative_path("foo/bar", None)
def test_accepts_path_with_dot_prefix(self):
validate_relative_path("./foo", None)
def test_accepts_nested_path_with_dot_prefix(self):
validate_relative_path("./foo/bar", None)
def test_accepts_internal_parent_ref_within_context(self):
validate_relative_path("foo/../bar", None)
def test_accepts_current_directory(self):
validate_relative_path(".", None)
def test_accepts_glob_patterns(self):
validate_relative_path("*.txt", None)
validate_relative_path("**/*.ts", None)
validate_relative_path("src/**/*", None)
def test_accepts_filenames_starting_with_double_dots(self):
validate_relative_path("..myconfig", None)
validate_relative_path("..cache", None)
validate_relative_path("...something", None)
validate_relative_path("foo/..myconfig", None)
class TestValidateRelativePathInvalidAbsolute:
"""Test cases for invalid absolute paths."""
def test_rejects_unix_absolute_path(self):
with pytest.raises(TemplateException) as excinfo:
validate_relative_path("/absolute/path", None)
assert "absolute paths are not allowed" in str(excinfo.value)
def test_rejects_root_path(self):
with pytest.raises(TemplateException):
validate_relative_path("/", None)
@pytest.mark.skipif(not is_windows, reason="Windows path test only runs on Windows")
def test_rejects_windows_drive_letter_path(self):
with pytest.raises(TemplateException) as excinfo:
validate_relative_path("C:\\Windows\\System32", None)
assert "absolute paths are not allowed" in str(excinfo.value)
class TestValidateRelativePathInvalidEscape:
"""Test cases for paths that escape the context directory."""
def test_rejects_simple_parent_directory_escape(self):
with pytest.raises(TemplateException) as excinfo:
validate_relative_path("../foo", None)
assert "path escapes the context directory" in str(excinfo.value)
def test_rejects_double_parent_directory_escape(self):
with pytest.raises(TemplateException):
validate_relative_path("../../foo", None)
def test_rejects_nested_parent_refs_escape(self):
with pytest.raises(TemplateException):
validate_relative_path("foo/../../bar", None)
def test_rejects_dot_prefix_escape(self):
with pytest.raises(TemplateException):
validate_relative_path("./foo/../../../bar", None)
def test_rejects_just_parent_directory(self):
with pytest.raises(TemplateException):
validate_relative_path("..", None)
def test_rejects_current_directory_followed_by_parent(self):
with pytest.raises(TemplateException):
validate_relative_path("./..", None)
def test_rejects_deeply_nested_escape(self):
with pytest.raises(TemplateException):
validate_relative_path("a/b/c/../../../../escape", None)
class TestValidateRelativePathErrorMessages:
"""Test cases for error message content."""
def test_absolute_path_error_includes_path(self):
with pytest.raises(TemplateException) as excinfo:
validate_relative_path("/etc/passwd", None)
assert "/etc/passwd" in str(excinfo.value)
def test_escape_path_error_includes_path(self):
with pytest.raises(TemplateException) as excinfo:
validate_relative_path("../secret", None)
assert "../secret" in str(excinfo.value)