173 lines
5.8 KiB
Python
173 lines
5.8 KiB
Python
import pytest
|
|
|
|
from e2b import AsyncSandbox
|
|
from e2b.exceptions import InvalidArgumentException
|
|
from e2b.sandbox.filesystem.filesystem import WriteEntry
|
|
|
|
|
|
async def test_write_file_with_metadata(async_sandbox: AsyncSandbox, debug):
|
|
filename = "test_metadata.txt"
|
|
content = "This is a test file with metadata."
|
|
metadata = {"author": "mish", "purpose": "upload"}
|
|
|
|
info = await async_sandbox.files.write(filename, content, metadata=metadata)
|
|
assert info.metadata == metadata
|
|
|
|
# Metadata is persisted and surfaced on subsequent reads.
|
|
stat = await async_sandbox.files.get_info(filename)
|
|
assert stat.metadata == metadata
|
|
|
|
if debug:
|
|
await async_sandbox.files.remove(filename)
|
|
|
|
|
|
async def test_write_file_with_metadata_octet_stream(
|
|
async_sandbox: AsyncSandbox, debug
|
|
):
|
|
filename = "test_metadata_octet.txt"
|
|
content = "This is a test file with metadata."
|
|
metadata = {"author": "mish", "purpose": "upload"}
|
|
|
|
info = await async_sandbox.files.write(
|
|
filename, content, metadata=metadata, use_octet_stream=True
|
|
)
|
|
assert info.metadata == metadata
|
|
|
|
stat = await async_sandbox.files.get_info(filename)
|
|
assert stat.metadata == metadata
|
|
|
|
if debug:
|
|
await async_sandbox.files.remove(filename)
|
|
|
|
|
|
async def test_write_file_without_metadata(async_sandbox: AsyncSandbox, debug):
|
|
filename = "test_no_metadata.txt"
|
|
|
|
info = await async_sandbox.files.write(filename, "no metadata here")
|
|
assert info.metadata is None
|
|
|
|
stat = await async_sandbox.files.get_info(filename)
|
|
assert stat.metadata is None
|
|
|
|
if debug:
|
|
await async_sandbox.files.remove(filename)
|
|
|
|
|
|
async def test_write_files_applies_metadata_to_every_file(
|
|
async_sandbox: AsyncSandbox, debug
|
|
):
|
|
# The same metadata is applied to every file in the upload.
|
|
metadata = {"source": "test-suite"}
|
|
files = [
|
|
WriteEntry(path="metadata_multi_1.txt", data="File 1"),
|
|
WriteEntry(path="metadata_multi_2.txt", data="File 2"),
|
|
]
|
|
|
|
infos = await async_sandbox.files.write_files(files, metadata=metadata)
|
|
assert len(infos) == len(files)
|
|
|
|
for info in infos:
|
|
assert info.metadata == metadata
|
|
stat = await async_sandbox.files.get_info(info.path)
|
|
assert stat.metadata == metadata
|
|
|
|
if debug:
|
|
for file in files:
|
|
await async_sandbox.files.remove(file["path"])
|
|
|
|
|
|
async def test_metadata_surfaced_when_listing(async_sandbox: AsyncSandbox, debug):
|
|
dirname = "metadata_list_dir"
|
|
filename = "listed.txt"
|
|
metadata = {"tag": "listed"}
|
|
|
|
await async_sandbox.files.make_dir(dirname)
|
|
await async_sandbox.files.write(
|
|
f"{dirname}/{filename}", "content", metadata=metadata
|
|
)
|
|
|
|
entries = await async_sandbox.files.list(dirname)
|
|
entry = next((e for e in entries if e.name == filename), None)
|
|
assert entry is not None
|
|
assert entry.metadata == metadata
|
|
|
|
if debug:
|
|
await async_sandbox.files.remove(dirname)
|
|
|
|
|
|
async def test_metadata_surfaced_after_rename(async_sandbox: AsyncSandbox, debug):
|
|
old_path = "metadata_rename_old.txt"
|
|
new_path = "metadata_rename_new.txt"
|
|
metadata = {"stage": "renamed"}
|
|
|
|
await async_sandbox.files.write(old_path, "content", metadata=metadata)
|
|
info = await async_sandbox.files.rename(old_path, new_path)
|
|
assert info.metadata == metadata
|
|
|
|
if debug:
|
|
await async_sandbox.files.remove(new_path)
|
|
|
|
|
|
async def test_overwriting_clears_stale_metadata(async_sandbox: AsyncSandbox, debug):
|
|
filename = "metadata_overwrite.txt"
|
|
|
|
await async_sandbox.files.write(filename, "first", metadata={"author": "mish"})
|
|
|
|
# Overwriting without metadata removes the previously stored metadata.
|
|
info = await async_sandbox.files.write(filename, "second")
|
|
assert info.metadata is None
|
|
|
|
stat = await async_sandbox.files.get_info(filename)
|
|
assert stat.metadata is None
|
|
|
|
if debug:
|
|
await async_sandbox.files.remove(filename)
|
|
|
|
|
|
async def test_metadata_set_via_xattrs_surfaced_in_get_info(
|
|
async_sandbox: AsyncSandbox, debug
|
|
):
|
|
filename = "metadata_xattr.txt"
|
|
await async_sandbox.files.write(filename, "content")
|
|
|
|
cmd = await async_sandbox.commands.run(f"realpath {filename}")
|
|
file_path = cmd.stdout.strip()
|
|
|
|
# Set an xattr directly in the `user.e2b.` namespace (out-of-band, not via
|
|
# the SDK upload); it should surface as metadata (with the namespace prefix
|
|
# stripped) when reading the file info.
|
|
await async_sandbox.commands.run(
|
|
f"python3 -c \"import os; os.setxattr('{file_path}', 'user.e2b.author', b'mish')\""
|
|
)
|
|
|
|
info = await async_sandbox.files.get_info(filename)
|
|
assert info.metadata == {"author": "mish"}
|
|
|
|
if debug:
|
|
await async_sandbox.files.remove(filename)
|
|
|
|
|
|
async def test_write_rejects_invalid_metadata(async_sandbox: AsyncSandbox):
|
|
filename = "invalid_metadata.txt"
|
|
|
|
# Key with a space is not a valid HTTP header token.
|
|
with pytest.raises(InvalidArgumentException):
|
|
await async_sandbox.files.write(filename, "x", metadata={"bad key": "value"})
|
|
|
|
# Empty key.
|
|
with pytest.raises(InvalidArgumentException):
|
|
await async_sandbox.files.write(filename, "x", metadata={"": "value"})
|
|
|
|
# Value with a non-printable / non-ASCII character.
|
|
with pytest.raises(InvalidArgumentException):
|
|
await async_sandbox.files.write(filename, "x", metadata={"good": "bad\nvalue"})
|
|
|
|
# Trailing newline (Python's `$` would accept it; `\Z` must not).
|
|
with pytest.raises(InvalidArgumentException):
|
|
await async_sandbox.files.write(filename, "x", metadata={"good": "value\n"})
|
|
with pytest.raises(InvalidArgumentException):
|
|
await async_sandbox.files.write(filename, "x", metadata={"key\n": "value"})
|
|
|
|
# The file must not have been created by a rejected write.
|
|
assert not await async_sandbox.files.exists(filename)
|