Skip to content

Commit

Permalink
add tests for BoxRef implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
boblat committed Jul 16, 2024
1 parent c4c946a commit 1ab1aef
Show file tree
Hide file tree
Showing 5 changed files with 477 additions and 43 deletions.
53 changes: 32 additions & 21 deletions src/algopy_testing/box.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ def length(self) -> algopy.UInt64:
raise ValueError("Box has not been created")
return algopy.UInt64(len(context.get_box(self._key)))

def _cast_to_type(self, value: algopy.Bytes) -> _TValue:
def _cast_to_type(self, value: bytes) -> _TValue:
"""
assuming _TValue to be one of the followings:
- algopy.UInt64
Expand All @@ -121,7 +121,7 @@ def _cast_to_type(self, value: algopy.Bytes) -> _TValue:
return self._type.from_bytes(value) # type: ignore[attr-defined, no-any-return]
elif self._type is algopy.UInt64:
return algopy.op.btoi(value) # type: ignore[return-value]
return value # type: ignore[return-value]
return algopy.Bytes(value) # type: ignore[return-value]

def _cast_to_bytes(self, value: _TValue) -> algopy.Bytes:
"""
Expand Down Expand Up @@ -184,10 +184,10 @@ def create(self, *, size: algopy.UInt64 | int) -> bool:
if size_int > MAX_BOX_SIZE:
raise ValueError(f"Box size cannot exceed {MAX_BOX_SIZE}")

box_content, box_exists = self.maybe()
box_content, box_exists = self._maybe()
if box_exists and len(box_content) != size_int:
raise ValueError("Box already exists with a different size")
if not box_exists:
if box_exists:
return False
context = get_test_context()
context.set_box(self._key, b"\x00" * size_int)
Expand All @@ -211,15 +211,17 @@ def extract(
:arg start_index: The offset to start extracting bytes from
:arg length: The number of bytes to extract
"""
import algopy

box_content, box_exists = self.maybe()
box_content, box_exists = self._maybe()
start_int = int(start_index)
length_int = int(length)
if not box_exists:
raise ValueError("Box does not exist")
if (start_int + length_int) > len(box_content):
raise ValueError("Index out of bounds")
return box_content[start_int : start_int + length_int]
result = box_content[start_int : start_int + length_int]
return algopy.Bytes(result)

def resize(self, new_size: algopy.UInt64 | int) -> None:
"""
Expand All @@ -232,10 +234,10 @@ def resize(self, new_size: algopy.UInt64 | int) -> None:
new_size_int = int(new_size)

if new_size_int > MAX_BOX_SIZE:
raise ValueError("Invalid box size")
box_content, box_exists = self.maybe()
raise ValueError(f"Box size cannot exceed {MAX_BOX_SIZE}")
box_content, box_exists = self._maybe()
if not box_exists:
raise ValueError("Box does not exist")
raise ValueError("Box has not been created")
if new_size_int > len(box_content):
updated_content = box_content + b"\x00" * (new_size_int - len(box_content))
else:
Expand All @@ -251,9 +253,9 @@ def replace(self, start_index: algopy.UInt64 | int, value: algopy.Bytes | bytes)
:arg value: The bytes to be written
"""
context = get_test_context()
box_content, box_exists = self.maybe()
box_content, box_exists = self._maybe()
if not box_exists:
raise ValueError("Box does not exist")
raise ValueError("Box has not been created")
start = int(start_index)
length = len(value)
if (start + length) > len(box_content):
Expand Down Expand Up @@ -282,14 +284,14 @@ def splice(
import algopy

context = get_test_context()
box_content, box_exists = self.maybe()
box_content, box_exists = self._maybe()

start = int(start_index)
delete_count = int(length)
insert_content = value.value if isinstance(value, algopy.Bytes) else value

if not box_exists:
raise ValueError("Box does not exist")
raise ValueError("Box has not been created")

if start > len(box_content):
raise ValueError("Start index exceeds box size")
Expand Down Expand Up @@ -320,9 +322,9 @@ def get(self, *, default: algopy.Bytes | bytes) -> algopy.Bytes:
"""
import algopy

box_content, box_exists = self.maybe()
box_content, box_exists = self._maybe()
default_bytes = default if isinstance(default, algopy.Bytes) else algopy.Bytes(default)
return default_bytes if not box_exists else box_content
return default_bytes if not box_exists else algopy.Bytes(box_content)

def put(self, value: algopy.Bytes | bytes) -> None:
"""
Expand All @@ -331,19 +333,27 @@ def put(self, value: algopy.Bytes | bytes) -> None:
:arg value: The value to write to the box
"""
import algopy

box_content, box_exists = self.maybe()
box_content, box_exists = self._maybe()
if box_exists and len(box_content) != len(value):
raise ValueError("Box already exists with a different size")

context = get_test_context()
context.set_box(self._key, value)
content = value if isinstance(value, algopy.Bytes) else algopy.Bytes(value)
context.set_box(self._key, content)

def maybe(self) -> tuple[algopy.Bytes, bool]:
"""
Retrieve the contents of the box if it exists, and return a boolean indicating if the box
exists.
"""
import algopy

box_content, box_exists = self._maybe()
return (algopy.Bytes(box_content), box_exists)

def _maybe(self) -> tuple[bytes, bool]:
context = get_test_context()
box_exists = context.does_box_exist(self._key)
box_content = context.get_box(self._key)
Expand All @@ -356,7 +366,7 @@ def length(self) -> algopy.UInt64:
"""
import algopy

box_content, box_exists = self.maybe()
box_content, box_exists = self._maybe()
if not box_exists:
raise ValueError("Box has not been created")
return algopy.UInt64(len(box_content))
Expand Down Expand Up @@ -463,19 +473,20 @@ def length(self, key: _TKey) -> algopy.UInt64:
:arg key: The key of the box to get
"""
import algopy

context = get_test_context()
key_bytes = self._full_key(key)
box_exists = context.does_box_exist(key_bytes)
if not box_exists:
raise ValueError("Box has not been created")
box_content_bytes = context.get_box(key_bytes)
return box_content_bytes.length
return algopy.UInt64(len(box_content_bytes))

def _full_key(self, key: _TKey) -> algopy.Bytes:
return self._key_prefix + self._cast_to_bytes(key)

def _cast_to_value_type(self, value: algopy.Bytes) -> _TValue:
def _cast_to_value_type(self, value: bytes) -> _TValue:
"""
assuming _TValue to be one of the followings:
- algopy.UInt64
Expand All @@ -490,7 +501,7 @@ def _cast_to_value_type(self, value: algopy.Bytes) -> _TValue:
return self._value_type.from_bytes(value) # type: ignore[attr-defined, no-any-return]
elif self._value_type is algopy.UInt64:
return algopy.op.btoi(value) # type: ignore[return-value]
return value # type: ignore[return-value]
return algopy.Bytes(value) # type: ignore[return-value]

def _cast_to_bytes(self, value: _TValue | _TKey) -> algopy.Bytes:
"""
Expand Down
10 changes: 4 additions & 6 deletions src/algopy_testing/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ def __init__(
self._scratch_spaces: dict[str, list[algopy.Bytes | algopy.UInt64 | bytes | int]] = {}
self._template_vars: dict[str, Any] = template_vars or {}
self._blocks: dict[int, dict[str, int]] = {}
self._boxes: dict[bytes, algopy.Bytes] = {}
self._boxes: dict[bytes, bytes] = {}
self._lsigs: dict[algopy.LogicSig, Callable[[], algopy.UInt64 | bool]] = {}
self._active_lsig_args: Sequence[algopy.Bytes] = []

Expand Down Expand Up @@ -1052,20 +1052,18 @@ def does_box_exist(self, name: algopy.Bytes | bytes) -> bool:
name_bytes = name if isinstance(name, bytes) else name.value
return name_bytes in self._boxes

def get_box(self, name: algopy.Bytes | bytes) -> algopy.Bytes:
def get_box(self, name: algopy.Bytes | bytes) -> bytes:
"""Get the content of a box."""
import algopy

name_bytes = name if isinstance(name, bytes) else name.value
return self._boxes.get(name_bytes, algopy.Bytes(b""))
return self._boxes.get(name_bytes, b"")

def set_box(self, name: algopy.Bytes | bytes, content: algopy.Bytes | bytes) -> None:
"""Set the content of a box."""
import algopy

name_bytes = name if isinstance(name, bytes) else name.value
content_bytes = content if isinstance(content, bytes) else content.value
self._boxes[name_bytes] = algopy.Bytes(content_bytes)
self._boxes[name_bytes] = content_bytes

def execute_logicsig(
self, lsig: algopy.LogicSig, lsig_args: Sequence[algopy.Bytes] | None = None
Expand Down
13 changes: 8 additions & 5 deletions src/algopy_testing/models/box.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,16 +48,18 @@ def extract(
box_content = context.get_box(name_bytes)
if not box_content:
raise ValueError("Box does not exist")
return box_content[start : start + length]
result = box_content[start : start + length]
return algopy.Bytes(result)

@staticmethod
def get(a: algopy.Bytes | bytes, /) -> tuple[algopy.Bytes, bool]:
import algopy

context = get_test_context()
name_bytes = a.value if isinstance(a, algopy.Bytes) else a
box_content = context.get_box(name_bytes)
return box_content, bool(box_content)
box_content = algopy.Bytes(context.get_box(name_bytes))
box_exists = context.does_box_exist(name_bytes)
return box_content, box_exists

@staticmethod
def length(a: algopy.Bytes | bytes, /) -> tuple[algopy.UInt64, bool]:
Expand All @@ -66,7 +68,8 @@ def length(a: algopy.Bytes | bytes, /) -> tuple[algopy.UInt64, bool]:
context = get_test_context()
name_bytes = a.value if isinstance(a, algopy.Bytes) else a
box_content = context.get_box(name_bytes)
return algopy.UInt64(len(box_content)), bool(box_content)
box_exists = context.does_box_exist(name_bytes)
return algopy.UInt64(len(box_content)), box_exists

@staticmethod
def put(a: algopy.Bytes | bytes, b: algopy.Bytes | bytes, /) -> None:
Expand All @@ -78,7 +81,7 @@ def put(a: algopy.Bytes | bytes, b: algopy.Bytes | bytes, /) -> None:
existing_content = context.get_box(name_bytes)
if existing_content and len(existing_content) != len(content):
raise ValueError("New content length does not match existing box length")
context.set_box(name_bytes, content)
context.set_box(name_bytes, algopy.Bytes(content))

@staticmethod
def replace(
Expand Down
24 changes: 13 additions & 11 deletions tests/test_box.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
from algopy_testing.primitives.uint64 import UInt64
from algopy_testing.utils import as_bytes, as_string

BOX_NOT_CREATED_ERROR = "Box has not been created"


@pytest.fixture()
def context() -> Generator[AlgopyTestContext, None, None]:
Expand All @@ -31,18 +33,18 @@ def context() -> Generator[AlgopyTestContext, None, None]:
(arc4.DynamicArray, b""),
],
)
def test_box_init_without_key(
def test_init_without_key(
context: AlgopyTestContext, # noqa: ARG001
value_type: type,
key: bytes | str | Bytes | String,
) -> None:
box = Box(value_type, key=key) # type: ignore[var-annotated]
assert not bool(box)
assert len(box.key) > 0
with pytest.raises(ValueError, match="Box has not been created"):
with pytest.raises(ValueError, match=BOX_NOT_CREATED_ERROR):
_ = box.value

with pytest.raises(ValueError, match="Box has not been created"):
with pytest.raises(ValueError, match=BOX_NOT_CREATED_ERROR):
_ = box.length


Expand All @@ -57,7 +59,7 @@ def test_box_init_without_key(
(arc4.DynamicArray, b"Key"),
],
)
def test_box_init_with_key(
def test_init_with_key(
context: AlgopyTestContext, # noqa: ARG001
value_type: type,
key: bytes | str | Bytes | String,
Expand All @@ -71,10 +73,10 @@ def test_box_init_with_key(
)
assert box.key == key_bytes

with pytest.raises(ValueError, match="Box has not been created"):
with pytest.raises(ValueError, match=BOX_NOT_CREATED_ERROR):
_ = box.value

with pytest.raises(ValueError, match="Box has not been created"):
with pytest.raises(ValueError, match=BOX_NOT_CREATED_ERROR):
_ = box.length


Expand All @@ -89,7 +91,7 @@ def test_box_init_with_key(
(arc4.DynamicArray[arc4.UInt64], arc4.DynamicArray(*[arc4.UInt64(100), arc4.UInt64(200)])),
],
)
def test_box_value_setter(
def test_value_setter(
context: AlgopyTestContext, # noqa: ARG001
value_type: type,
value: typing.Any,
Expand Down Expand Up @@ -117,7 +119,7 @@ def test_box_value_setter(
(arc4.DynamicArray[arc4.UInt64], arc4.DynamicArray(*[arc4.UInt64(100), arc4.UInt64(200)])),
],
)
def test_box_value_deleter(
def test_value_deleter(
context: AlgopyTestContext, # noqa: ARG001
value_type: type,
value: typing.Any,
Expand All @@ -129,7 +131,7 @@ def test_box_value_deleter(
del box.value
assert not bool(box)

with pytest.raises(ValueError, match="Box has not been created"):
with pytest.raises(ValueError, match=BOX_NOT_CREATED_ERROR):
_ = box.value

op_box_content, op_box_exists = algopy.op.Box.get(key)
Expand All @@ -148,7 +150,7 @@ def test_box_value_deleter(
(arc4.DynamicArray[arc4.UInt64], arc4.DynamicArray(*[arc4.UInt64(100), arc4.UInt64(200)])),
],
)
def test_box_maybe(
def test_maybe(
context: AlgopyTestContext, # noqa: ARG001
value_type: type,
value: typing.Any,
Expand Down Expand Up @@ -177,7 +179,7 @@ def test_box_maybe(
(arc4.DynamicArray[arc4.UInt64], arc4.DynamicArray(*[arc4.UInt64(100), arc4.UInt64(200)])),
],
)
def test_box_maybe_when_value_does_not_exist(
def test_maybe_when_box_does_not_exist(
context: AlgopyTestContext, # noqa: ARG001
value_type: type,
value: typing.Any,
Expand Down
Loading

0 comments on commit 1ab1aef

Please sign in to comment.