v1.1.0 - Added validation and line formatting, fixed zero-length padding. Added tests.
This commit is contained in:
@@ -0,0 +1,206 @@
|
||||
"""Tests for base64url module."""
|
||||
|
||||
import binascii
|
||||
|
||||
import pytest
|
||||
|
||||
import base64url
|
||||
|
||||
|
||||
class TestEnc:
|
||||
"""Tests for enc() function."""
|
||||
|
||||
def test_empty(self):
|
||||
assert base64url.enc(b"") == ""
|
||||
|
||||
def test_single_byte(self):
|
||||
assert base64url.enc(b"\x00") == "AA"
|
||||
|
||||
def test_no_padding_needed(self):
|
||||
# 3 bytes encodes to 4 chars, no padding
|
||||
assert base64url.enc(b"abc") == "YWJj"
|
||||
|
||||
def test_one_padding_stripped(self):
|
||||
# 2 bytes encodes to 3 chars + 1 padding
|
||||
assert base64url.enc(b"ab") == "YWI"
|
||||
assert "=" not in base64url.enc(b"ab")
|
||||
|
||||
def test_two_padding_stripped(self):
|
||||
# 1 byte encodes to 2 chars + 2 padding
|
||||
assert base64url.enc(b"a") == "YQ"
|
||||
assert "=" not in base64url.enc(b"a")
|
||||
|
||||
def test_urlsafe_chars(self):
|
||||
# Bytes that produce + and / in standard Base64 should use - and _
|
||||
# 0xfb -> standard: +, urlsafe: -
|
||||
# 0xff -> standard: /, urlsafe: _
|
||||
data = b"\xfb\xff\xfe"
|
||||
result = base64url.enc(data)
|
||||
assert "+" not in result
|
||||
assert "/" not in result
|
||||
assert "-" in result or "_" in result
|
||||
|
||||
def test_all_bytes(self):
|
||||
# Verify all byte values encode/decode correctly
|
||||
data = bytes(range(256))
|
||||
encoded = base64url.enc(data)
|
||||
assert "+" not in encoded
|
||||
assert "/" not in encoded
|
||||
assert "=" not in encoded
|
||||
|
||||
|
||||
class TestDec:
|
||||
"""Tests for dec() function."""
|
||||
|
||||
def test_empty(self):
|
||||
assert base64url.dec("") == b""
|
||||
|
||||
def test_single_byte(self):
|
||||
assert base64url.dec("AA") == b"\x00"
|
||||
|
||||
def test_without_padding(self):
|
||||
assert base64url.dec("YWJj") == b"abc"
|
||||
assert base64url.dec("YWI") == b"ab"
|
||||
assert base64url.dec("YQ") == b"a"
|
||||
|
||||
def test_with_padding(self):
|
||||
# Padding should be optional
|
||||
assert base64url.dec("YWI=") == b"ab"
|
||||
assert base64url.dec("YQ==") == b"a"
|
||||
|
||||
def test_urlsafe_chars(self):
|
||||
# Should accept - instead of +, _ instead of /
|
||||
assert base64url.dec("--__") == base64url.dec("++//")
|
||||
|
||||
def test_standard_base64_chars(self):
|
||||
# Should also accept standard Base64 with + and /
|
||||
assert base64url.dec("++//") == b"\xfb\xef\xff"
|
||||
|
||||
def test_mixed_chars(self):
|
||||
# Mix of standard and urlsafe should work
|
||||
assert base64url.dec("+-/_") is not None
|
||||
|
||||
def test_invalid_char_raises(self):
|
||||
# Invalid characters should raise binascii.Error
|
||||
with pytest.raises(binascii.Error):
|
||||
base64url.dec("!!!!")
|
||||
|
||||
def test_invalid_char_in_middle_raises(self):
|
||||
with pytest.raises(binascii.Error):
|
||||
base64url.dec("YW!j")
|
||||
|
||||
def test_whitespace_raises(self):
|
||||
# Whitespace should not be silently ignored
|
||||
with pytest.raises(binascii.Error):
|
||||
base64url.dec("YW Jj")
|
||||
|
||||
def test_newline_raises(self):
|
||||
with pytest.raises(binascii.Error):
|
||||
base64url.dec("YWJj\n")
|
||||
|
||||
|
||||
class TestRoundtrip:
|
||||
"""Tests for enc/dec roundtrip."""
|
||||
|
||||
def test_empty_roundtrip(self):
|
||||
assert base64url.dec(base64url.enc(b"")) == b""
|
||||
|
||||
def test_hello_roundtrip(self):
|
||||
assert base64url.dec(base64url.enc(b"hello")) == b"hello"
|
||||
|
||||
def test_binary_roundtrip(self):
|
||||
data = bytes(range(256))
|
||||
assert base64url.dec(base64url.enc(data)) == data
|
||||
|
||||
def test_various_lengths(self):
|
||||
# Test lengths 0-20 to cover all padding scenarios
|
||||
for i in range(21):
|
||||
data = bytes(range(i))
|
||||
assert base64url.dec(base64url.enc(data)) == data
|
||||
|
||||
def test_random_bytes(self):
|
||||
import os
|
||||
|
||||
for _ in range(10):
|
||||
data = os.urandom(100)
|
||||
assert base64url.dec(base64url.enc(data)) == data
|
||||
|
||||
|
||||
class TestEncLines:
|
||||
"""Tests for enc_lines() function."""
|
||||
|
||||
def test_empty(self):
|
||||
assert base64url.enc_lines(b"") == ""
|
||||
|
||||
def test_short_no_wrap(self):
|
||||
# Short data fits on one line
|
||||
result = base64url.enc_lines(b"hello", length=76)
|
||||
assert "\n" not in result
|
||||
assert result == "aGVsbG8"
|
||||
|
||||
def test_wrapping(self):
|
||||
# 60 bytes -> 80 base64 chars, should wrap at 76
|
||||
data = b"x" * 60
|
||||
result = base64url.enc_lines(data, length=76)
|
||||
lines = result.split("\n")
|
||||
assert len(lines) == 2
|
||||
assert len(lines[0]) == 76
|
||||
assert len(lines[1]) == 4
|
||||
|
||||
def test_custom_line_length(self):
|
||||
data = b"hello world"
|
||||
result = base64url.enc_lines(data, length=4)
|
||||
lines = result.split("\n")
|
||||
assert all(len(line) <= 4 for line in lines)
|
||||
|
||||
def test_exact_multiple(self):
|
||||
# 6 bytes -> 8 base64 chars, exactly 2 lines of 4
|
||||
data = b"abcdef"
|
||||
result = base64url.enc_lines(data, length=4)
|
||||
lines = result.split("\n")
|
||||
assert len(lines) == 2
|
||||
assert all(len(line) == 4 for line in lines)
|
||||
|
||||
|
||||
class TestDecLines:
|
||||
"""Tests for dec_lines() function."""
|
||||
|
||||
def test_empty(self):
|
||||
assert base64url.dec_lines("") == b""
|
||||
|
||||
def test_single_line(self):
|
||||
assert base64url.dec_lines("aGVsbG8") == b"hello"
|
||||
|
||||
def test_multiline(self):
|
||||
multiline = "aGVs\nbG8"
|
||||
assert base64url.dec_lines(multiline) == b"hello"
|
||||
|
||||
def test_spaces(self):
|
||||
spaced = "aGVs bG8"
|
||||
assert base64url.dec_lines(spaced) == b"hello"
|
||||
|
||||
def test_tabs(self):
|
||||
tabbed = "aGVs\tbG8"
|
||||
assert base64url.dec_lines(tabbed) == b"hello"
|
||||
|
||||
def test_mixed_whitespace(self):
|
||||
mixed = " aGVs\n\t bG8 \n"
|
||||
assert base64url.dec_lines(mixed) == b"hello"
|
||||
|
||||
def test_crlf(self):
|
||||
crlf = "aGVs\r\nbG8"
|
||||
assert base64url.dec_lines(crlf) == b"hello"
|
||||
|
||||
|
||||
class TestLinesRoundtrip:
|
||||
"""Tests for enc_lines/dec_lines roundtrip."""
|
||||
|
||||
def test_roundtrip(self):
|
||||
data = bytes(range(256))
|
||||
assert base64url.dec_lines(base64url.enc_lines(data)) == data
|
||||
|
||||
def test_roundtrip_various_line_lengths(self):
|
||||
data = b"hello world" * 10
|
||||
for line_length in [4, 16, 64, 76, 100]:
|
||||
encoded = base64url.enc_lines(data, length=line_length)
|
||||
assert base64url.dec_lines(encoded) == data
|
||||
Reference in New Issue
Block a user