Apply black formatter to all Python files

Reformatted 29 files for consistent code style and CI compliance.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Aaron D. Lee
2026-01-02 17:44:41 -05:00
parent 221678d934
commit afa88bc73b
29 changed files with 2067 additions and 1814 deletions

View File

@@ -41,8 +41,8 @@ def sample_images(temp_dir):
images = []
for i in range(3):
img_path = temp_dir / f"test_image_{i}.png"
img = Image.new('RGB', (100, 100), color=(i * 50, i * 50, i * 50))
img.save(img_path, 'PNG')
img = Image.new("RGB", (100, 100), color=(i * 50, i * 50, i * 50))
img.save(img_path, "PNG")
images.append(img_path)
return images
@@ -55,9 +55,9 @@ def sample_reference_photo():
from PIL import Image
img = Image.new('RGB', (100, 100), color=(128, 128, 128))
img = Image.new("RGB", (100, 100), color=(128, 128, 128))
buf = BytesIO()
img.save(buf, 'PNG')
img.save(buf, "PNG")
return buf.getvalue()
@@ -67,7 +67,7 @@ def sample_credentials(sample_reference_photo):
return {
"reference_photo": sample_reference_photo,
"passphrase": "test phrase four words", # v3.2.0: single passphrase
"pin": "123456"
"pin": "123456",
}
@@ -95,9 +95,9 @@ class TestBatchItem:
message="Done",
)
result = item.to_dict()
assert result['input_path'] == "input.png"
assert result['output_path'] == "output.png"
assert result['status'] == "success"
assert result["input_path"] == "input.png"
assert result["output_path"] == "output.png"
assert result["status"] == "success"
class TestBatchResult:
@@ -106,11 +106,12 @@ class TestBatchResult:
def test_to_json(self):
"""Should serialize to valid JSON."""
import json
result = BatchResult(operation="encode", total=5, succeeded=4, failed=1)
json_str = result.to_json()
parsed = json.loads(json_str)
assert parsed['operation'] == "encode"
assert parsed['summary']['total'] == 5
assert parsed["operation"] == "encode"
assert parsed["summary"]["total"] == 5
def test_duration_with_end_time(self):
"""Duration should work when end_time is set."""
@@ -128,7 +129,7 @@ class TestBatchCredentials:
data = {
"reference_photo": sample_reference_photo,
"passphrase": "test phrase four words",
"pin": "123456"
"pin": "123456",
}
creds = BatchCredentials.from_dict(data)
assert creds.passphrase == "test phrase four words"
@@ -139,7 +140,7 @@ class TestBatchCredentials:
data = {
"reference_photo": sample_reference_photo,
"day_phrase": "legacy phrase here", # Old key name
"pin": "123456"
"pin": "123456",
}
creds = BatchCredentials.from_dict(data)
# Should accept old key and map to passphrase
@@ -151,19 +152,19 @@ class TestBatchCredentials:
creds = BatchCredentials(
reference_photo=sample_reference_photo,
passphrase="test phrase four words",
pin="123456"
pin="123456",
)
result = creds.to_dict()
assert result['passphrase'] == "test phrase four words"
assert result['pin'] == "123456"
assert 'day_phrase' not in result # Old key should not be present
assert result["passphrase"] == "test phrase four words"
assert result["pin"] == "123456"
assert "day_phrase" not in result # Old key should not be present
def test_passphrase_is_string(self, sample_reference_photo):
"""Passphrase should be a string, not a dict."""
creds = BatchCredentials(
reference_photo=sample_reference_photo,
passphrase="test phrase four words",
pin="123456"
pin="123456",
)
assert isinstance(creds.passphrase, str)
@@ -216,7 +217,7 @@ class TestBatchProcessor:
nested = temp_dir / "nested"
nested.mkdir()
img_path = nested / "nested.png"
img = Image.new('RGB', (50, 50))
img = Image.new("RGB", (50, 50))
img.save(img_path)
processor = BatchProcessor()
@@ -241,7 +242,9 @@ class TestBatchProcessor:
message="test",
)
def test_batch_encode_accepts_passphrase_credentials(self, sample_images, temp_dir, sample_credentials):
def test_batch_encode_accepts_passphrase_credentials(
self, sample_images, temp_dir, sample_credentials
):
"""Should accept v3.2.0 format credentials with passphrase."""
processor = BatchProcessor()
result = processor.batch_encode(
@@ -343,9 +346,9 @@ class TestBatchCapacityCheck:
"""Results should include capacity info."""
results = batch_capacity_check(sample_images)
for item in results:
assert 'capacity_bytes' in item
assert 'dimensions' in item
assert 'valid' in item
assert "capacity_bytes" in item
assert "dimensions" in item
assert "valid" in item
def test_handles_invalid_files(self, temp_dir):
"""Should handle non-image files gracefully."""
@@ -354,7 +357,7 @@ class TestBatchCapacityCheck:
results = batch_capacity_check([bad_file])
assert len(results) == 1
assert 'error' in results[0]
assert "error" in results[0]
class TestPrintBatchResult:
@@ -403,7 +406,7 @@ class TestCredentialsMigration:
old_format = {
"reference_photo": sample_reference_photo,
"phrase": "old style phrase",
"pin": "123456"
"pin": "123456",
}
# Should not raise
creds = BatchCredentials.from_dict(old_format)
@@ -414,7 +417,7 @@ class TestCredentialsMigration:
old_format = {
"reference_photo": sample_reference_photo,
"day_phrase": "old day phrase",
"pin": "123456"
"pin": "123456",
}
creds = BatchCredentials.from_dict(old_format)
assert creds.passphrase == "old day phrase"
@@ -425,7 +428,7 @@ class TestCredentialsMigration:
"reference_photo": sample_reference_photo,
"passphrase": "new style passphrase",
"day_phrase": "old day phrase",
"pin": "123456"
"pin": "123456",
}
creds = BatchCredentials.from_dict(mixed_format)
assert creds.passphrase == "new style passphrase"

View File

@@ -42,6 +42,7 @@ class TestCompress:
def test_compress_incompressible_data(self):
"""Incompressible data should be stored uncompressed."""
import os
# Random data doesn't compress well
data = os.urandom(500)
result = compress(data, CompressionAlgorithm.ZLIB)
@@ -107,6 +108,7 @@ class TestDecompress:
def test_roundtrip_large_data(self):
"""Large data should survive compress/decompress roundtrip."""
import os
original = os.urandom(50000)
compressed = compress(original)
result = decompress(compressed)
@@ -173,7 +175,7 @@ class TestEdgeCases:
def test_unicode_after_encoding(self):
"""UTF-8 encoded Unicode should compress correctly."""
text = "Hello, 世界! 🎉 " * 100
data = text.encode('utf-8')
data = text.encode("utf-8")
compressed = compress(data)
result = decompress(compressed)
assert result.decode('utf-8') == text
assert result.decode("utf-8") == text

View File

@@ -37,12 +37,13 @@ from stegasoo.steganography import get_output_format
# Fixtures
# =============================================================================
@pytest.fixture
def png_image():
"""Create a test PNG image."""
img = Image.new('RGB', (100, 100), color='red')
img = Image.new("RGB", (100, 100), color="red")
buf = io.BytesIO()
img.save(buf, format='PNG')
img.save(buf, format="PNG")
buf.seek(0)
return buf.getvalue()
@@ -50,9 +51,9 @@ def png_image():
@pytest.fixture
def large_png_image():
"""Create a larger test PNG image for DCT mode."""
img = Image.new('RGB', (400, 400), color='blue')
img = Image.new("RGB", (400, 400), color="blue")
buf = io.BytesIO()
img.save(buf, format='PNG')
img.save(buf, format="PNG")
buf.seek(0)
return buf.getvalue()
@@ -60,9 +61,9 @@ def large_png_image():
@pytest.fixture
def bmp_image():
"""Create a test BMP image."""
img = Image.new('RGB', (100, 100), color='blue')
img = Image.new("RGB", (100, 100), color="blue")
buf = io.BytesIO()
img.save(buf, format='BMP')
img.save(buf, format="BMP")
buf.seek(0)
return buf.getvalue()
@@ -70,9 +71,9 @@ def bmp_image():
@pytest.fixture
def jpeg_image():
"""Create a test JPEG image."""
img = Image.new('RGB', (100, 100), color='green')
img = Image.new("RGB", (100, 100), color="green")
buf = io.BytesIO()
img.save(buf, format='JPEG')
img.save(buf, format="JPEG")
buf.seek(0)
return buf.getvalue()
@@ -80,9 +81,9 @@ def jpeg_image():
@pytest.fixture
def gif_image():
"""Create a test GIF image."""
img = Image.new('RGB', (100, 100), color='yellow')
img = Image.new("RGB", (100, 100), color="yellow")
buf = io.BytesIO()
img.save(buf, format='GIF')
img.save(buf, format="GIF")
buf.seek(0)
return buf.getvalue()
@@ -91,6 +92,7 @@ def gif_image():
# Key Generation Tests (v3.2.0 Updated)
# =============================================================================
class TestKeygen:
"""Tests for key generation functions."""
@@ -99,7 +101,7 @@ class TestKeygen:
pin = generate_pin()
assert len(pin) == 6
assert pin.isdigit()
assert pin[0] != '0'
assert pin[0] != "0"
def test_generate_pin_lengths(self):
"""PIN generation should work for all valid lengths."""
@@ -129,7 +131,7 @@ class TestKeygen:
# v3.2.0: Single passphrase instead of 7 daily phrases
assert creds.passphrase is not None
assert isinstance(creds.passphrase, str)
assert ' ' in creds.passphrase # Should have multiple words
assert " " in creds.passphrase # Should have multiple words
def test_generate_credentials_rsa_only(self):
"""RSA-only credentials should have single passphrase."""
@@ -180,6 +182,7 @@ class TestKeygen:
# Validation Tests (v3.2.0 Updated)
# =============================================================================
class TestValidation:
"""Tests for validation functions."""
@@ -250,56 +253,59 @@ class TestValidation:
# Output Format Tests
# =============================================================================
class TestOutputFormat:
"""Tests for output format handling."""
def test_png_stays_png(self):
"""PNG input should produce PNG output."""
fmt, ext = get_output_format('PNG')
assert fmt == 'PNG'
assert ext == 'png'
fmt, ext = get_output_format("PNG")
assert fmt == "PNG"
assert ext == "png"
def test_bmp_stays_bmp(self):
"""BMP input should produce BMP output."""
fmt, ext = get_output_format('BMP')
assert fmt == 'BMP'
assert ext == 'bmp'
fmt, ext = get_output_format("BMP")
assert fmt == "BMP"
assert ext == "bmp"
def test_jpeg_becomes_png(self):
"""JPEG input should produce PNG output (lossless)."""
fmt, ext = get_output_format('JPEG')
assert fmt == 'PNG'
assert ext == 'png'
fmt, ext = get_output_format("JPEG")
assert fmt == "PNG"
assert ext == "png"
def test_gif_becomes_png(self):
"""GIF input should produce PNG output."""
fmt, ext = get_output_format('GIF')
assert fmt == 'PNG'
assert ext == 'png'
fmt, ext = get_output_format("GIF")
assert fmt == "PNG"
assert ext == "png"
def test_none_becomes_png(self):
"""None format should default to PNG."""
fmt, ext = get_output_format(None)
assert fmt == 'PNG'
assert ext == 'png'
assert fmt == "PNG"
assert ext == "png"
def test_unknown_becomes_png(self):
"""Unknown format should default to PNG."""
fmt, ext = get_output_format('UNKNOWN')
assert fmt == 'PNG'
assert ext == 'png'
fmt, ext = get_output_format("UNKNOWN")
assert fmt == "PNG"
assert ext == "png"
# =============================================================================
# Header Overhead Test (v4.0.0)
# =============================================================================
class TestConstants:
"""Tests for constants and configuration."""
def test_header_overhead_value(self):
"""Header overhead should be 66 bytes (v4.0.0: added flags byte)."""
from stegasoo.steganography import HEADER_OVERHEAD
assert HEADER_OVERHEAD == 66
@@ -307,6 +313,7 @@ class TestConstants:
# Encode/Decode Tests (v4.0.0 Updated)
# =============================================================================
class TestEncodeDecode:
"""Tests for encoding and decoding functions."""
@@ -322,19 +329,19 @@ class TestEncodeDecode:
reference_photo=png_image,
carrier_image=png_image,
passphrase=passphrase,
pin=pin
pin=pin,
)
assert result.stego_image is not None
assert len(result.stego_image) > 0
assert result.filename.endswith('.png')
assert result.filename.endswith(".png")
# v3.2.0: Use passphrase parameter, no date_str
decoded = decode(
stego_image=result.stego_image,
reference_photo=png_image,
passphrase=passphrase,
pin=pin
pin=pin,
)
assert decoded.message == message
@@ -350,7 +357,7 @@ class TestEncodeDecode:
reference_photo=png_image,
carrier_image=png_image,
passphrase=passphrase,
pin=pin
pin=pin,
)
# decode_text returns string directly
@@ -358,7 +365,7 @@ class TestEncodeDecode:
stego_image=result.stego_image,
reference_photo=png_image,
passphrase=passphrase,
pin=pin
pin=pin,
)
assert decoded_text == message
@@ -370,9 +377,9 @@ class TestEncodeDecode:
reference_photo=png_image,
carrier_image=png_image,
passphrase="test phrase here now",
pin="123456"
pin="123456",
)
assert result.filename.endswith('.png')
assert result.filename.endswith(".png")
def test_bmp_carrier_produces_bmp(self, bmp_image, png_image):
"""BMP carrier should produce BMP output."""
@@ -381,9 +388,9 @@ class TestEncodeDecode:
reference_photo=png_image,
carrier_image=bmp_image,
passphrase="test phrase here now",
pin="123456"
pin="123456",
)
assert result.filename.endswith('.bmp')
assert result.filename.endswith(".bmp")
def test_jpeg_carrier_produces_png(self, jpeg_image, png_image):
"""JPEG carrier should produce PNG output (lossless)."""
@@ -392,9 +399,9 @@ class TestEncodeDecode:
reference_photo=png_image,
carrier_image=jpeg_image,
passphrase="test phrase here now",
pin="123456"
pin="123456",
)
assert result.filename.endswith('.png')
assert result.filename.endswith(".png")
def test_bmp_roundtrip(self, bmp_image, png_image):
"""Full encode/decode cycle with BMP should work."""
@@ -407,15 +414,15 @@ class TestEncodeDecode:
reference_photo=png_image,
carrier_image=bmp_image,
passphrase=passphrase,
pin=pin
pin=pin,
)
assert result.filename.endswith('.bmp')
assert result.filename.endswith(".bmp")
decoded = decode(
stego_image=result.stego_image,
reference_photo=png_image,
passphrase=passphrase,
pin=pin
pin=pin,
)
assert decoded.message == message
@@ -427,7 +434,7 @@ class TestEncodeDecode:
reference_photo=png_image,
carrier_image=png_image,
passphrase="test phrase here now",
pin="123456"
pin="123456",
)
with pytest.raises((stegasoo.DecryptionError, stegasoo.ExtractionError)):
@@ -435,7 +442,7 @@ class TestEncodeDecode:
stego_image=result.stego_image,
reference_photo=png_image,
passphrase="test phrase here now",
pin="654321" # Wrong PIN
pin="654321", # Wrong PIN
)
def test_wrong_passphrase_fails(self, png_image):
@@ -445,7 +452,7 @@ class TestEncodeDecode:
reference_photo=png_image,
carrier_image=png_image,
passphrase="correct phrase here now",
pin="123456"
pin="123456",
)
with pytest.raises((stegasoo.DecryptionError, stegasoo.ExtractionError)):
@@ -453,7 +460,7 @@ class TestEncodeDecode:
stego_image=result.stego_image,
reference_photo=png_image,
passphrase="wrong phrase here now", # Wrong passphrase
pin="123456"
pin="123456",
)
def test_unicode_message(self, png_image):
@@ -467,14 +474,14 @@ class TestEncodeDecode:
reference_photo=png_image,
carrier_image=png_image,
passphrase=passphrase,
pin=pin
pin=pin,
)
decoded = decode(
stego_image=result.stego_image,
reference_photo=png_image,
passphrase=passphrase,
pin=pin
pin=pin,
)
assert decoded.message == message
@@ -486,18 +493,20 @@ class TestEncodeDecode:
reference_photo=png_image,
carrier_image=png_image,
passphrase="test phrase here now",
pin="123456"
pin="123456",
)
# Filename format: {random_hex}_{YYYYMMDD}.{ext}
# e.g., "a1b2c3d4_20251227.png"
import re
assert re.search(r'^[a-f0-9]{8}_\d{8}\.png$', result.filename)
assert re.search(r"^[a-f0-9]{8}_\d{8}\.png$", result.filename)
# =============================================================================
# DCT Mode Tests (v3.2.0)
# =============================================================================
class TestDCTMode:
"""Tests for DCT steganography mode."""
@@ -519,7 +528,7 @@ class TestDCTMode:
carrier_image=large_png_image,
passphrase=passphrase,
pin=pin,
embed_mode='dct'
embed_mode="dct",
)
assert result.stego_image is not None
@@ -528,7 +537,7 @@ class TestDCTMode:
stego_image=result.stego_image,
reference_photo=large_png_image,
passphrase=passphrase,
pin=pin
pin=pin,
)
assert decoded.message == message
@@ -545,7 +554,7 @@ class TestDCTMode:
carrier_image=large_png_image,
passphrase=passphrase,
pin=pin,
embed_mode='dct'
embed_mode="dct",
)
# Decode with auto mode (default)
@@ -554,7 +563,7 @@ class TestDCTMode:
reference_photo=large_png_image,
passphrase=passphrase,
pin=pin,
embed_mode='auto'
embed_mode="auto",
)
assert decoded.message == message
@@ -564,19 +573,20 @@ class TestDCTMode:
# Version Tests
# =============================================================================
class TestVersion:
"""Tests for version information."""
def test_version_exists(self):
"""Version string should exist and be valid."""
assert hasattr(stegasoo, '__version__')
parts = stegasoo.__version__.split('.')
assert hasattr(stegasoo, "__version__")
parts = stegasoo.__version__.split(".")
assert len(parts) >= 2
assert all(p.isdigit() for p in parts[:2])
def test_version_is_4_0_0(self):
"""Version should be 4.0.0 or higher."""
parts = stegasoo.__version__.split('.')
parts = stegasoo.__version__.split(".")
major = int(parts[0])
assert major >= 4
@@ -585,6 +595,7 @@ class TestVersion:
# Backward Compatibility Tests
# =============================================================================
class TestBackwardCompatibility:
"""Tests for backward compatibility handling."""
@@ -596,7 +607,7 @@ class TestBackwardCompatibility:
reference_photo=png_image,
carrier_image=png_image,
day_phrase="old style phrase", # Old parameter name
pin="123456"
pin="123456",
)
def test_old_date_str_parameter_raises(self, png_image):
@@ -608,7 +619,7 @@ class TestBackwardCompatibility:
carrier_image=png_image,
passphrase="test phrase here now",
pin="123456",
date_str="2025-01-01" # Removed parameter
date_str="2025-01-01", # Removed parameter
)
@@ -616,6 +627,7 @@ class TestBackwardCompatibility:
# Channel Key Tests (v4.0.0)
# =============================================================================
class TestChannelKey:
"""Tests for channel key functionality (v4.0.0)."""
@@ -624,7 +636,7 @@ class TestChannelKey:
key = generate_channel_key()
# Format: XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX (8 groups of 4)
assert len(key) == 39
parts = key.split('-')
parts = key.split("-")
assert len(parts) == 8
for part in parts:
assert len(part) == 4
@@ -665,7 +677,7 @@ class TestChannelKey:
carrier_image=png_image,
passphrase=passphrase,
pin=pin,
channel_key=channel_key
channel_key=channel_key,
)
assert result.stego_image is not None
@@ -676,7 +688,7 @@ class TestChannelKey:
reference_photo=png_image,
passphrase=passphrase,
pin=pin,
channel_key=channel_key
channel_key=channel_key,
)
assert decoded.message == message
@@ -696,7 +708,7 @@ class TestChannelKey:
carrier_image=png_image,
passphrase=passphrase,
pin=pin,
channel_key=channel_key1
channel_key=channel_key1,
)
# Decode with different channel key should fail
@@ -706,7 +718,7 @@ class TestChannelKey:
reference_photo=png_image,
passphrase=passphrase,
pin=pin,
channel_key=channel_key2
channel_key=channel_key2,
)
def test_encode_decode_public_mode(self, png_image):
@@ -722,7 +734,7 @@ class TestChannelKey:
carrier_image=png_image,
passphrase=passphrase,
pin=pin,
channel_key="" # Explicit public mode
channel_key="", # Explicit public mode
)
# Decode without channel key
@@ -731,7 +743,7 @@ class TestChannelKey:
reference_photo=png_image,
passphrase=passphrase,
pin=pin,
channel_key="" # Explicit public mode
channel_key="", # Explicit public mode
)
assert decoded.message == message
@@ -749,7 +761,7 @@ class TestChannelKey:
carrier_image=png_image,
passphrase=passphrase,
pin=pin,
channel_key="" # Public mode
channel_key="", # Public mode
)
# Decode with channel key should fail
@@ -760,5 +772,5 @@ class TestChannelKey:
reference_photo=png_image,
passphrase=passphrase,
pin=pin,
channel_key=channel_key
channel_key=channel_key,
)