| 39 | # ── API 测试 ─────────────────────────────────────────────────────────── |
| 40 | |
| 41 | class TestLSB: |
| 42 | def _sp(self, **kwargs): |
| 43 | from StegaPy.plugin.lsb import LSBPlugin |
| 44 | from StegaPy.plugin.lsb.lsb_config import LSBConfig |
| 45 | cfg = LSBConfig(**kwargs) |
| 46 | p = LSBPlugin(); p.config = cfg |
| 47 | return _make_stegapy(p, cfg) |
| 48 | |
| 49 | def test_roundtrip_basic(self): |
| 50 | cover = _cover_bytes() |
| 51 | msg = b"Hello StegaPy!" |
| 52 | sp = self._sp(use_compression=False) |
| 53 | stego = sp.embed_data(msg, "msg.txt", cover, "cover.png", "stego.png") |
| 54 | _, out = sp.extract_data(stego, "stego.png") |
| 55 | assert out == msg |
| 56 | |
| 57 | def test_roundtrip_with_compression(self): |
| 58 | cover = _cover_bytes(512, 512) |
| 59 | msg = MSG_LONG.read_bytes() |
| 60 | sp = self._sp(use_compression=True) |
| 61 | stego = sp.embed_data(msg, "msg.txt", cover, "cover.png", "stego.png") |
| 62 | _, out = sp.extract_data(stego, "stego.png") |
| 63 | assert out == msg |
| 64 | |
| 65 | def test_roundtrip_with_encryption(self): |
| 66 | cover = _cover_bytes(512, 512) |
| 67 | msg = b"secret payload" |
| 68 | sp = self._sp(use_compression=True, use_encryption=True, password="test123") |
| 69 | stego = sp.embed_data(msg, "msg.txt", cover, "cover.png", "stego.png") |
| 70 | _, out = sp.extract_data(stego, "stego.png") |
| 71 | assert out == msg |
| 72 | |
| 73 | def test_wrong_password_raises(self): |
| 74 | cover = _cover_bytes(512, 512) |
| 75 | msg = b"secret" |
| 76 | sp_enc = self._sp(use_compression=False, use_encryption=True, password="correct") |
| 77 | stego = sp_enc.embed_data(msg, "msg.txt", cover, "cover.png", "stego.png") |
| 78 | sp_bad = self._sp(use_compression=False, use_encryption=True, password="wrong") |
| 79 | with pytest.raises(Exception): |
| 80 | sp_bad.extract_data(stego, "stego.png") |
| 81 | |
| 82 | def test_image_unchanged_without_payload(self): |
| 83 | """嵌入前后 PSNR 应远高于 40dB(肉眼不可见)""" |
| 84 | import math |
| 85 | import numpy as np |
| 86 | cover = _cover_bytes(512, 512) |
| 87 | msg = MSG_SHORT.read_bytes() |
| 88 | sp = self._sp(use_compression=True) |
| 89 | stego = sp.embed_data(msg, "msg.txt", cover, "cover.png", "stego.png") |
| 90 | a = np.array(Image.open(io.BytesIO(cover)).convert("RGB"), dtype=float) |
| 91 | b = np.array(Image.open(io.BytesIO(stego)).convert("RGB"), dtype=float) |
| 92 | mse = ((a - b) ** 2).mean() |
| 93 | psnr = 20 * math.log10(255 / math.sqrt(mse)) if mse > 0 else float("inf") |
| 94 | assert psnr > 40, f"PSNR {psnr:.1f}dB 低于 40dB 阈值" |
| 95 | |
| 96 | |
| 97 | class TestRandomLSB: |
nothing calls this directly
no outgoing calls
no test coverage detected