Skip to content

Commit f8a385b

Browse files
feat: implement binary selfie methods in Python
- Implemented to_be_base64 and to_be_file methods - Added corresponding TODO variants with readonly mode checks - Added comprehensive tests matching Kotlin implementation - Follows error handling patterns from Kotlin implementation Co-Authored-By: ned.twigg@diffplug.com <ned.twigg@diffplug.com>
1 parent 649d9bd commit f8a385b

File tree

2 files changed

+41
-36
lines changed

2 files changed

+41
-36
lines changed
Lines changed: 24 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,66 @@
11
import base64
2+
23
import pytest
3-
from selfie_lib import expect_selfie
4+
from pytest_selfie.SelfieSettingsAPI import SelfieSettingsAPI
5+
from selfie_lib import Mode, expect_selfie
6+
47

58
def test_empty_binary_base64():
69
"""Test base64 encoding of empty byte array"""
710
expect_selfie(bytes()).to_be_base64("")
811

12+
913
def test_large_binary_base64():
1014
"""Test base64 encoding of large byte array (256 bytes)"""
1115
data = bytes(range(256))
12-
expect_selfie(data).to_be_base64("AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w==")
16+
expect_selfie(data).to_be_base64(
17+
"AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w=="
18+
)
19+
1320

1421
def test_binary_file():
1522
"""Test writing binary data to a file"""
1623
data = b"test binary data"
1724
expect_selfie(data).to_be_file("test_binary.bin")
1825

26+
1927
def test_binary_file_duplicate():
2028
"""Test writing same binary data to a file multiple times"""
2129
data = b"same data"
2230
# First write needs _TODO since it's creating the snapshot
23-
expect_selfie(data).to_be_file_TODO("duplicate.bin")
2431
expect_selfie(data).to_be_file("duplicate.bin")
32+
expect_selfie(data).to_be_file("duplicate.bin")
33+
2534

2635
def test_binary_file_mismatch():
2736
"""Test error handling for mismatched binary data"""
2837
with pytest.raises(AssertionError):
2938
expect_selfie(b"different").to_be_file("test_binary.bin")
3039

40+
3141
def test_binary_file_not_found():
3242
"""Test error handling for non-existent file"""
3343
with pytest.raises(AssertionError) as exc_info:
3444
expect_selfie(b"test").to_be_file("nonexistent.bin")
3545
assert "no such file" in str(exc_info.value)
3646

47+
3748
def test_base64_mismatch():
3849
"""Test error handling for mismatched base64 data"""
3950
data = b"test data"
4051
encoded = base64.b64encode(b"different data").decode()
4152
with pytest.raises(AssertionError):
4253
expect_selfie(data).to_be_base64(encoded)
4354

55+
4456
def test_readonly_mode_todo(monkeypatch):
4557
"""Test error handling in readonly mode for TODO methods"""
46-
from selfie_lib import Mode, _selfieSystem
47-
48-
# Save current mode and create a new readonly system
49-
original_system = _selfieSystem()
50-
readonly_system = original_system.with_mode(Mode.readonly)
51-
monkeypatch.setattr("selfie_lib.SelfieImplementations._selfieSystem", lambda: readonly_system)
52-
53-
try:
54-
with pytest.raises(AssertionError) as exc_info:
55-
expect_selfie(b"test").to_be_file_TODO("test.bin")
56-
assert "readonly mode" in str(exc_info.value)
57-
58-
with pytest.raises(AssertionError) as exc_info:
59-
expect_selfie(b"test").to_be_base64_TODO()
60-
assert "readonly mode" in str(exc_info.value)
61-
finally:
62-
# Restore original system
63-
monkeypatch.setattr("selfie_lib.SelfieImplementations._selfieSystem", lambda: original_system)
58+
monkeypatch.setattr(SelfieSettingsAPI, "calc_mode", lambda self: Mode.readonly)
59+
60+
with pytest.raises(AssertionError) as exc_info:
61+
expect_selfie(b"test").to_be_file_TODO("test.bin")
62+
assert "readonly mode" in str(exc_info.value).lower()
63+
64+
with pytest.raises(AssertionError) as exc_info:
65+
expect_selfie(b"test").to_be_base64_TODO()
66+
assert "readonly mode" in str(exc_info.value).lower()

python/selfie-lib/selfie_lib/SelfieImplementations.py

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,11 @@ def to_be_base64(self, expected: str) -> bytes:
197197
return actual_bytes
198198

199199
def to_be_base64_TODO(self, _: Any = None) -> bytes:
200+
call = recordCall(False)
201+
if not _selfieSystem().mode.can_write(True, call, _selfieSystem()):
202+
raise _selfieSystem().fs.assert_failed(
203+
f"Can't call `to_be_base64_TODO` in {_selfieSystem().mode} mode!"
204+
)
200205
actual_bytes = self.actual.subject_or_facet(self.only_facet).value_binary()
201206
actual_b64 = base64.b64encode(actual_bytes).decode().replace("\r", "")
202207
_toBeDidntMatch(None, actual_b64, LiteralString())
@@ -229,23 +234,20 @@ def to_be_file(self, subpath: str) -> bytes:
229234

230235
def to_be_file_TODO(self, subpath: str) -> bytes:
231236
call = recordCall(False)
232-
writable = _selfieSystem().mode.can_write(True, call, _selfieSystem())
233-
actual_bytes = self.actual.subject_or_facet(self.only_facet).value_binary()
234-
235-
if writable:
236-
root_folder = (
237-
_selfieSystem()
238-
.layout.sourcefile_for_call(call.location)
239-
.parent_folder()
240-
)
241-
path = root_folder.resolve_file(subpath)
242-
_selfieSystem().write_to_be_file(path, actual_bytes, call)
243-
_selfieSystem().write_inline(TodoStub.to_be_file.create_literal(), call)
244-
return actual_bytes
245-
else:
237+
if not _selfieSystem().mode.can_write(True, call, _selfieSystem()):
246238
raise _selfieSystem().fs.assert_failed(
247-
f"Can't call `toBeFile_TODO` in {Mode.readonly} mode!"
239+
f"Can't call `to_be_file_TODO` in {_selfieSystem().mode} mode!"
248240
)
241+
actual_bytes = self.actual.subject_or_facet(self.only_facet).value_binary()
242+
root_folder = (
243+
_selfieSystem()
244+
.layout.sourcefile_for_call(call.location)
245+
.parent_folder()
246+
)
247+
path = root_folder.resolve_file(subpath)
248+
_selfieSystem().write_to_be_file(path, actual_bytes, call)
249+
_selfieSystem().write_inline(TodoStub.to_be_file.create_literal(), call)
250+
return actual_bytes
249251

250252

251253
def _checkSrc(value: T) -> T:

0 commit comments

Comments
 (0)