Lint cleanup: ruff fixes across entire codebase
- Strip trailing whitespace from all Python files - Fix import sorting (I001) across all modules - Convert Optional[X] to X | None syntax (UP045) - Remove unused imports (F401) - Convert lambda assignments to def functions (E731) - Add TYPE_CHECKING import for forward references - Update pyproject.toml ruff config: - Move select/ignore to [tool.ruff.lint] section - Add per-file ignores for DCT colorspace naming (N803/N806) - Add per-file ignores for __init__.py import structure (E402) - Exclude defunct test_routes.py - Remove frontends/web/test_routes.py (defunct debug snippet) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -7,18 +7,19 @@ Updated for v4.0.0:
|
||||
- BatchCredentials.passphrase is a single string
|
||||
"""
|
||||
|
||||
import pytest
|
||||
import tempfile
|
||||
import shutil
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from unittest.mock import Mock, patch
|
||||
from unittest.mock import Mock
|
||||
|
||||
import pytest
|
||||
|
||||
from stegasoo.batch import (
|
||||
BatchCredentials,
|
||||
BatchItem,
|
||||
BatchProcessor,
|
||||
BatchResult,
|
||||
BatchItem,
|
||||
BatchStatus,
|
||||
BatchCredentials,
|
||||
batch_capacity_check,
|
||||
print_batch_result,
|
||||
)
|
||||
@@ -36,14 +37,14 @@ def temp_dir():
|
||||
def sample_images(temp_dir):
|
||||
"""Create sample PNG images for testing."""
|
||||
from PIL import Image
|
||||
|
||||
|
||||
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')
|
||||
images.append(img_path)
|
||||
|
||||
|
||||
return images
|
||||
|
||||
|
||||
@@ -58,19 +59,19 @@ def sample_credentials():
|
||||
|
||||
class TestBatchItem:
|
||||
"""Tests for BatchItem dataclass."""
|
||||
|
||||
|
||||
def test_duration_calculation(self):
|
||||
"""Duration should be calculated from start/end times."""
|
||||
item = BatchItem(input_path=Path("test.png"))
|
||||
item.start_time = 100.0
|
||||
item.end_time = 105.5
|
||||
assert item.duration == 5.5
|
||||
|
||||
|
||||
def test_duration_none_without_times(self):
|
||||
"""Duration should be None if times not set."""
|
||||
item = BatchItem(input_path=Path("test.png"))
|
||||
assert item.duration is None
|
||||
|
||||
|
||||
def test_to_dict(self):
|
||||
"""to_dict should serialize all fields."""
|
||||
item = BatchItem(
|
||||
@@ -87,7 +88,7 @@ class TestBatchItem:
|
||||
|
||||
class TestBatchResult:
|
||||
"""Tests for BatchResult dataclass."""
|
||||
|
||||
|
||||
def test_to_json(self):
|
||||
"""Should serialize to valid JSON."""
|
||||
import json
|
||||
@@ -96,7 +97,7 @@ class TestBatchResult:
|
||||
parsed = json.loads(json_str)
|
||||
assert parsed['operation'] == "encode"
|
||||
assert parsed['summary']['total'] == 5
|
||||
|
||||
|
||||
def test_duration_with_end_time(self):
|
||||
"""Duration should work when end_time is set."""
|
||||
result = BatchResult(operation="test")
|
||||
@@ -107,7 +108,7 @@ class TestBatchResult:
|
||||
|
||||
class TestBatchCredentials:
|
||||
"""Tests for BatchCredentials dataclass (v3.2.0)."""
|
||||
|
||||
|
||||
def test_from_dict_new_format(self):
|
||||
"""Should parse v3.2.0 format with 'passphrase' key."""
|
||||
data = {
|
||||
@@ -117,7 +118,7 @@ class TestBatchCredentials:
|
||||
creds = BatchCredentials.from_dict(data)
|
||||
assert creds.passphrase == "test phrase four words"
|
||||
assert creds.pin == "123456"
|
||||
|
||||
|
||||
def test_from_dict_legacy_format(self):
|
||||
"""Should parse legacy format with 'day_phrase' key for migration."""
|
||||
data = {
|
||||
@@ -128,7 +129,7 @@ class TestBatchCredentials:
|
||||
# Should accept old key and map to passphrase
|
||||
assert creds.passphrase == "legacy phrase here"
|
||||
assert creds.pin == "123456"
|
||||
|
||||
|
||||
def test_to_dict(self):
|
||||
"""Should serialize to v3.2.0 format."""
|
||||
creds = BatchCredentials(
|
||||
@@ -139,7 +140,7 @@ class TestBatchCredentials:
|
||||
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):
|
||||
"""Passphrase should be a string, not a dict."""
|
||||
creds = BatchCredentials(
|
||||
@@ -151,59 +152,59 @@ class TestBatchCredentials:
|
||||
|
||||
class TestBatchProcessor:
|
||||
"""Tests for BatchProcessor class."""
|
||||
|
||||
|
||||
def test_init_default_workers(self):
|
||||
"""Should default to 4 workers."""
|
||||
processor = BatchProcessor()
|
||||
assert processor.max_workers == 4
|
||||
|
||||
|
||||
def test_init_custom_workers(self):
|
||||
"""Should accept custom worker count."""
|
||||
processor = BatchProcessor(max_workers=8)
|
||||
assert processor.max_workers == 8
|
||||
|
||||
|
||||
def test_is_valid_image_png(self, temp_dir):
|
||||
"""Should recognize PNG as valid."""
|
||||
processor = BatchProcessor()
|
||||
png_path = temp_dir / "test.png"
|
||||
png_path.touch()
|
||||
assert processor._is_valid_image(png_path)
|
||||
|
||||
|
||||
def test_is_valid_image_txt(self, temp_dir):
|
||||
"""Should reject non-image files."""
|
||||
processor = BatchProcessor()
|
||||
txt_path = temp_dir / "test.txt"
|
||||
txt_path.touch()
|
||||
assert not processor._is_valid_image(txt_path)
|
||||
|
||||
|
||||
def test_find_images_file(self, sample_images):
|
||||
"""Should find single image file."""
|
||||
processor = BatchProcessor()
|
||||
results = list(processor.find_images([sample_images[0]]))
|
||||
assert len(results) == 1
|
||||
assert results[0] == sample_images[0]
|
||||
|
||||
|
||||
def test_find_images_directory(self, sample_images, temp_dir):
|
||||
"""Should find images in directory."""
|
||||
processor = BatchProcessor()
|
||||
results = list(processor.find_images([temp_dir]))
|
||||
assert len(results) == 3
|
||||
|
||||
|
||||
def test_find_images_recursive(self, temp_dir):
|
||||
"""Should find images recursively."""
|
||||
from PIL import Image
|
||||
|
||||
|
||||
# Create nested directory
|
||||
nested = temp_dir / "nested"
|
||||
nested.mkdir()
|
||||
img_path = nested / "nested.png"
|
||||
img = Image.new('RGB', (50, 50))
|
||||
img.save(img_path)
|
||||
|
||||
|
||||
processor = BatchProcessor()
|
||||
results = list(processor.find_images([temp_dir], recursive=True))
|
||||
assert any(p.name == "nested.png" for p in results)
|
||||
|
||||
|
||||
def test_batch_encode_requires_message_or_file(self, sample_images, sample_credentials):
|
||||
"""Should raise if neither message nor file provided."""
|
||||
processor = BatchProcessor()
|
||||
@@ -212,7 +213,7 @@ class TestBatchProcessor:
|
||||
images=sample_images,
|
||||
credentials=sample_credentials,
|
||||
)
|
||||
|
||||
|
||||
def test_batch_encode_requires_credentials(self, sample_images):
|
||||
"""Should raise if credentials not provided."""
|
||||
processor = BatchProcessor()
|
||||
@@ -221,7 +222,7 @@ class TestBatchProcessor:
|
||||
images=sample_images,
|
||||
message="test",
|
||||
)
|
||||
|
||||
|
||||
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()
|
||||
@@ -231,11 +232,11 @@ class TestBatchProcessor:
|
||||
output_dir=temp_dir / "output",
|
||||
credentials=sample_credentials, # Uses 'passphrase' key
|
||||
)
|
||||
|
||||
|
||||
assert isinstance(result, BatchResult)
|
||||
assert result.operation == "encode"
|
||||
assert result.total == 3
|
||||
|
||||
|
||||
def test_batch_encode_creates_result(self, sample_images, temp_dir, sample_credentials):
|
||||
"""Should return BatchResult with correct structure."""
|
||||
processor = BatchProcessor()
|
||||
@@ -245,18 +246,18 @@ class TestBatchProcessor:
|
||||
output_dir=temp_dir / "output",
|
||||
credentials=sample_credentials,
|
||||
)
|
||||
|
||||
|
||||
assert isinstance(result, BatchResult)
|
||||
assert result.operation == "encode"
|
||||
assert result.total == 3
|
||||
assert len(result.items) == 3
|
||||
|
||||
|
||||
def test_batch_decode_requires_credentials(self, sample_images):
|
||||
"""Should raise if credentials not provided."""
|
||||
processor = BatchProcessor()
|
||||
with pytest.raises(ValueError, match="Credentials"):
|
||||
processor.batch_decode(images=sample_images)
|
||||
|
||||
|
||||
def test_batch_decode_accepts_passphrase_credentials(self, sample_images, sample_credentials):
|
||||
"""Should accept v3.2.0 format credentials with passphrase."""
|
||||
processor = BatchProcessor()
|
||||
@@ -264,11 +265,11 @@ class TestBatchProcessor:
|
||||
images=sample_images,
|
||||
credentials=sample_credentials, # Uses 'passphrase' key
|
||||
)
|
||||
|
||||
|
||||
assert isinstance(result, BatchResult)
|
||||
assert result.operation == "decode"
|
||||
assert result.total == 3
|
||||
|
||||
|
||||
def test_batch_decode_creates_result(self, sample_images, sample_credentials):
|
||||
"""Should return BatchResult with correct structure."""
|
||||
processor = BatchProcessor()
|
||||
@@ -276,30 +277,30 @@ class TestBatchProcessor:
|
||||
images=sample_images,
|
||||
credentials=sample_credentials,
|
||||
)
|
||||
|
||||
|
||||
assert isinstance(result, BatchResult)
|
||||
assert result.operation == "decode"
|
||||
assert result.total == 3
|
||||
|
||||
|
||||
def test_progress_callback_called(self, sample_images, sample_credentials):
|
||||
"""Progress callback should be called for each item."""
|
||||
processor = BatchProcessor()
|
||||
callback = Mock()
|
||||
|
||||
|
||||
processor.batch_encode(
|
||||
images=sample_images,
|
||||
message="Test",
|
||||
credentials=sample_credentials,
|
||||
progress_callback=callback,
|
||||
)
|
||||
|
||||
|
||||
assert callback.call_count == 3
|
||||
|
||||
|
||||
def test_custom_encode_func(self, sample_images, temp_dir, sample_credentials):
|
||||
"""Should use custom encode function if provided."""
|
||||
processor = BatchProcessor()
|
||||
encode_mock = Mock()
|
||||
|
||||
|
||||
processor.batch_encode(
|
||||
images=sample_images,
|
||||
message="Test",
|
||||
@@ -307,19 +308,19 @@ class TestBatchProcessor:
|
||||
credentials=sample_credentials,
|
||||
encode_func=encode_mock,
|
||||
)
|
||||
|
||||
|
||||
assert encode_mock.call_count == 3
|
||||
|
||||
|
||||
class TestBatchCapacityCheck:
|
||||
"""Tests for batch_capacity_check function."""
|
||||
|
||||
|
||||
def test_returns_list(self, sample_images):
|
||||
"""Should return list of results."""
|
||||
results = batch_capacity_check(sample_images)
|
||||
assert isinstance(results, list)
|
||||
assert len(results) == 3
|
||||
|
||||
|
||||
def test_includes_capacity(self, sample_images):
|
||||
"""Results should include capacity info."""
|
||||
results = batch_capacity_check(sample_images)
|
||||
@@ -327,12 +328,12 @@ class TestBatchCapacityCheck:
|
||||
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."""
|
||||
bad_file = temp_dir / "not_an_image.png"
|
||||
bad_file.write_bytes(b"not a png")
|
||||
|
||||
|
||||
results = batch_capacity_check([bad_file])
|
||||
assert len(results) == 1
|
||||
assert 'error' in results[0]
|
||||
@@ -340,7 +341,7 @@ class TestBatchCapacityCheck:
|
||||
|
||||
class TestPrintBatchResult:
|
||||
"""Tests for print_batch_result function."""
|
||||
|
||||
|
||||
def test_prints_summary(self, capsys, sample_images):
|
||||
"""Should print summary without errors."""
|
||||
result = BatchResult(
|
||||
@@ -350,14 +351,14 @@ class TestPrintBatchResult:
|
||||
failed=1,
|
||||
)
|
||||
result.end_time = result.start_time + 5.0
|
||||
|
||||
|
||||
print_batch_result(result)
|
||||
|
||||
|
||||
captured = capsys.readouterr()
|
||||
assert "ENCODE" in captured.out
|
||||
assert "3" in captured.out # total
|
||||
assert "2" in captured.out # succeeded
|
||||
|
||||
|
||||
def test_verbose_shows_items(self, capsys):
|
||||
"""Verbose mode should show individual items."""
|
||||
result = BatchResult(operation="decode", total=1, succeeded=1)
|
||||
@@ -369,16 +370,16 @@ class TestPrintBatchResult:
|
||||
)
|
||||
]
|
||||
result.end_time = result.start_time + 1.0
|
||||
|
||||
|
||||
print_batch_result(result, verbose=True)
|
||||
|
||||
|
||||
captured = capsys.readouterr()
|
||||
assert "test.png" in captured.out
|
||||
|
||||
|
||||
class TestCredentialsMigration:
|
||||
"""Tests for v3.1.x to v3.2.0 credentials migration."""
|
||||
|
||||
|
||||
def test_old_phrase_key_accepted(self):
|
||||
"""Old 'phrase' key should be accepted for migration."""
|
||||
old_format = {
|
||||
@@ -388,7 +389,7 @@ class TestCredentialsMigration:
|
||||
# Should not raise
|
||||
creds = BatchCredentials.from_dict(old_format)
|
||||
assert creds.passphrase == "old style phrase"
|
||||
|
||||
|
||||
def test_old_day_phrase_key_accepted(self):
|
||||
"""Old 'day_phrase' key should be accepted for migration."""
|
||||
old_format = {
|
||||
@@ -397,7 +398,7 @@ class TestCredentialsMigration:
|
||||
}
|
||||
creds = BatchCredentials.from_dict(old_format)
|
||||
assert creds.passphrase == "old day phrase"
|
||||
|
||||
|
||||
def test_new_passphrase_key_preferred(self):
|
||||
"""New 'passphrase' key should take precedence if both present."""
|
||||
mixed_format = {
|
||||
|
||||
@@ -3,24 +3,25 @@ Tests for Stegasoo compression module.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
from stegasoo.compression import (
|
||||
compress,
|
||||
decompress,
|
||||
CompressionAlgorithm,
|
||||
CompressionError,
|
||||
get_compression_ratio,
|
||||
estimate_compressed_size,
|
||||
get_available_algorithms,
|
||||
algorithm_name,
|
||||
MIN_COMPRESS_SIZE,
|
||||
COMPRESSION_MAGIC,
|
||||
HAS_LZ4,
|
||||
MIN_COMPRESS_SIZE,
|
||||
CompressionAlgorithm,
|
||||
CompressionError,
|
||||
algorithm_name,
|
||||
compress,
|
||||
decompress,
|
||||
estimate_compressed_size,
|
||||
get_available_algorithms,
|
||||
get_compression_ratio,
|
||||
)
|
||||
|
||||
|
||||
class TestCompress:
|
||||
"""Tests for compress function."""
|
||||
|
||||
|
||||
def test_compress_small_data_not_compressed(self):
|
||||
"""Small data should not be compressed (overhead not worth it)."""
|
||||
small_data = b"hello"
|
||||
@@ -28,7 +29,7 @@ class TestCompress:
|
||||
# Should have magic header but NONE algorithm
|
||||
assert result.startswith(COMPRESSION_MAGIC)
|
||||
assert result[4] == CompressionAlgorithm.NONE
|
||||
|
||||
|
||||
def test_compress_zlib_reduces_size(self):
|
||||
"""Zlib should reduce size for compressible data."""
|
||||
# Highly compressible data
|
||||
@@ -37,7 +38,7 @@ class TestCompress:
|
||||
assert len(result) < len(data)
|
||||
assert result.startswith(COMPRESSION_MAGIC)
|
||||
assert result[4] == CompressionAlgorithm.ZLIB
|
||||
|
||||
|
||||
def test_compress_incompressible_data(self):
|
||||
"""Incompressible data should be stored uncompressed."""
|
||||
import os
|
||||
@@ -46,7 +47,7 @@ class TestCompress:
|
||||
result = compress(data, CompressionAlgorithm.ZLIB)
|
||||
# Should fall back to NONE if compression didn't help
|
||||
assert result.startswith(COMPRESSION_MAGIC)
|
||||
|
||||
|
||||
def test_compress_none_algorithm(self):
|
||||
"""NONE algorithm should just wrap data."""
|
||||
data = b"Test data" * 100
|
||||
@@ -55,7 +56,7 @@ class TestCompress:
|
||||
assert result[4] == CompressionAlgorithm.NONE
|
||||
# Data should be after 9-byte header
|
||||
assert result[9:] == data
|
||||
|
||||
|
||||
@pytest.mark.skipif(not HAS_LZ4, reason="LZ4 not installed")
|
||||
def test_compress_lz4(self):
|
||||
"""LZ4 compression should work if available."""
|
||||
@@ -68,33 +69,33 @@ class TestCompress:
|
||||
|
||||
class TestDecompress:
|
||||
"""Tests for decompress function."""
|
||||
|
||||
|
||||
def test_decompress_zlib(self):
|
||||
"""Decompression should restore original data."""
|
||||
original = b"Hello, World! " * 100
|
||||
compressed = compress(original, CompressionAlgorithm.ZLIB)
|
||||
result = decompress(compressed)
|
||||
assert result == original
|
||||
|
||||
|
||||
def test_decompress_none(self):
|
||||
"""Uncompressed wrapped data should decompress correctly."""
|
||||
original = b"Small data"
|
||||
wrapped = compress(original, CompressionAlgorithm.NONE)
|
||||
result = decompress(wrapped)
|
||||
assert result == original
|
||||
|
||||
|
||||
def test_decompress_no_magic(self):
|
||||
"""Data without magic header should be returned as-is."""
|
||||
data = b"Not compressed at all"
|
||||
result = decompress(data)
|
||||
assert result == data
|
||||
|
||||
|
||||
def test_decompress_truncated_header(self):
|
||||
"""Truncated header should raise CompressionError."""
|
||||
bad_data = COMPRESSION_MAGIC + b"\x01" # Too short
|
||||
with pytest.raises(CompressionError, match="Truncated"):
|
||||
decompress(bad_data)
|
||||
|
||||
|
||||
@pytest.mark.skipif(not HAS_LZ4, reason="LZ4 not installed")
|
||||
def test_decompress_lz4(self):
|
||||
"""LZ4 decompression should work."""
|
||||
@@ -102,7 +103,7 @@ class TestDecompress:
|
||||
compressed = compress(original, CompressionAlgorithm.LZ4)
|
||||
result = decompress(compressed)
|
||||
assert result == original
|
||||
|
||||
|
||||
def test_roundtrip_large_data(self):
|
||||
"""Large data should survive compress/decompress roundtrip."""
|
||||
import os
|
||||
@@ -114,19 +115,19 @@ class TestDecompress:
|
||||
|
||||
class TestUtilities:
|
||||
"""Tests for utility functions."""
|
||||
|
||||
|
||||
def test_compression_ratio_compressed(self):
|
||||
"""Ratio should be < 1 for well-compressed data."""
|
||||
original = b"X" * 1000
|
||||
compressed = compress(original)
|
||||
ratio = get_compression_ratio(original, compressed)
|
||||
assert ratio < 1.0
|
||||
|
||||
|
||||
def test_compression_ratio_empty(self):
|
||||
"""Empty data should return ratio of 1.0."""
|
||||
ratio = get_compression_ratio(b"", b"")
|
||||
assert ratio == 1.0
|
||||
|
||||
|
||||
def test_estimate_compressed_size_small(self):
|
||||
"""Small data estimation should be accurate."""
|
||||
data = b"Test " * 100
|
||||
@@ -134,13 +135,13 @@ class TestUtilities:
|
||||
actual = len(compress(data))
|
||||
# Should be within 20% for small data
|
||||
assert abs(estimate - actual) / actual < 0.2
|
||||
|
||||
|
||||
def test_available_algorithms(self):
|
||||
"""Should always include NONE and ZLIB."""
|
||||
algos = get_available_algorithms()
|
||||
assert CompressionAlgorithm.NONE in algos
|
||||
assert CompressionAlgorithm.ZLIB in algos
|
||||
|
||||
|
||||
def test_algorithm_name(self):
|
||||
"""Algorithm names should be human-readable."""
|
||||
assert "Zlib" in algorithm_name(CompressionAlgorithm.ZLIB)
|
||||
@@ -150,25 +151,25 @@ class TestUtilities:
|
||||
|
||||
class TestEdgeCases:
|
||||
"""Edge case tests."""
|
||||
|
||||
|
||||
def test_empty_data(self):
|
||||
"""Empty data should be handled gracefully."""
|
||||
result = compress(b"")
|
||||
assert decompress(result) == b""
|
||||
|
||||
|
||||
def test_exact_min_size(self):
|
||||
"""Data at exactly MIN_COMPRESS_SIZE should be compressed."""
|
||||
data = b"x" * MIN_COMPRESS_SIZE
|
||||
result = compress(data, CompressionAlgorithm.ZLIB)
|
||||
assert result.startswith(COMPRESSION_MAGIC)
|
||||
assert decompress(result) == data
|
||||
|
||||
|
||||
def test_binary_data(self):
|
||||
"""Binary data with null bytes should work."""
|
||||
data = b"\x00\x01\x02\x03" * 500
|
||||
compressed = compress(data)
|
||||
assert decompress(compressed) == data
|
||||
|
||||
|
||||
def test_unicode_after_encoding(self):
|
||||
"""UTF-8 encoded Unicode should compress correctly."""
|
||||
text = "Hello, 世界! 🎉 " * 100
|
||||
|
||||
@@ -11,29 +11,28 @@ Updated for v4.0.0:
|
||||
- Python 3.12 recommended (3.13 not supported)
|
||||
"""
|
||||
|
||||
import io
|
||||
|
||||
import pytest
|
||||
from PIL import Image
|
||||
import io
|
||||
|
||||
import stegasoo
|
||||
from stegasoo import (
|
||||
generate_pin,
|
||||
generate_passphrase,
|
||||
generate_credentials,
|
||||
validate_pin,
|
||||
validate_message,
|
||||
validate_passphrase,
|
||||
validate_channel_key,
|
||||
encode,
|
||||
decode,
|
||||
decode_text,
|
||||
encode,
|
||||
generate_channel_key,
|
||||
generate_credentials,
|
||||
generate_passphrase,
|
||||
generate_pin,
|
||||
get_channel_fingerprint,
|
||||
__version__,
|
||||
validate_channel_key,
|
||||
validate_message,
|
||||
validate_passphrase,
|
||||
validate_pin,
|
||||
)
|
||||
from stegasoo.steganography import get_output_format
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Fixtures
|
||||
# =============================================================================
|
||||
@@ -94,7 +93,7 @@ def gif_image():
|
||||
|
||||
class TestKeygen:
|
||||
"""Tests for key generation functions."""
|
||||
|
||||
|
||||
def test_generate_pin_default(self):
|
||||
"""Default PIN should be 6 digits, no leading zero."""
|
||||
pin = generate_pin()
|
||||
@@ -183,7 +182,7 @@ class TestKeygen:
|
||||
|
||||
class TestValidation:
|
||||
"""Tests for validation functions."""
|
||||
|
||||
|
||||
def test_validate_pin_valid(self):
|
||||
"""Valid PIN should pass validation."""
|
||||
result = validate_pin("123456")
|
||||
@@ -253,7 +252,7 @@ class TestValidation:
|
||||
|
||||
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')
|
||||
@@ -310,7 +309,7 @@ class TestConstants:
|
||||
|
||||
class TestEncodeDecode:
|
||||
"""Tests for encoding and decoding functions."""
|
||||
|
||||
|
||||
def test_encode_decode_roundtrip(self, png_image):
|
||||
"""Full encode/decode cycle should work."""
|
||||
message = "Secret message!"
|
||||
@@ -501,7 +500,7 @@ class TestEncodeDecode:
|
||||
|
||||
class TestDCTMode:
|
||||
"""Tests for DCT steganography mode."""
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def skip_if_no_dct(self):
|
||||
"""Skip test if DCT support not available."""
|
||||
@@ -567,7 +566,7 @@ class TestDCTMode:
|
||||
|
||||
class TestVersion:
|
||||
"""Tests for version information."""
|
||||
|
||||
|
||||
def test_version_exists(self):
|
||||
"""Version string should exist and be valid."""
|
||||
assert hasattr(stegasoo, '__version__')
|
||||
@@ -588,7 +587,7 @@ class TestVersion:
|
||||
|
||||
class TestBackwardCompatibility:
|
||||
"""Tests for backward compatibility handling."""
|
||||
|
||||
|
||||
def test_old_day_phrase_parameter_raises(self, png_image):
|
||||
"""Using old day_phrase parameter should raise TypeError."""
|
||||
with pytest.raises(TypeError):
|
||||
|
||||
Reference in New Issue
Block a user