Move all string support functions to corelibs_strings and other modules
This commit is contained in:
@@ -9,6 +9,7 @@ dependencies = [
|
||||
"corelibs-csv>=1.0.0",
|
||||
"corelibs-datetime>=1.0.1",
|
||||
"corelibs-debug>=1.0.0",
|
||||
"corelibs-double-byte-format>=1.0.0",
|
||||
"corelibs-dump-data>=1.0.0",
|
||||
"corelibs-encryption>=1.0.0",
|
||||
"corelibs-enum-base>=1.0.0",
|
||||
@@ -20,6 +21,7 @@ dependencies = [
|
||||
"corelibs-requests>=1.0.0",
|
||||
"corelibs-search>=1.0.0",
|
||||
"corelibs-stack-trace>=1.0.0",
|
||||
"corelibs-strings>=1.0.0",
|
||||
"corelibs-text-colors>=1.0.0",
|
||||
"corelibs-var>=1.0.0",
|
||||
"cryptography>=46.0.3",
|
||||
|
||||
@@ -2,7 +2,11 @@
|
||||
Format bytes
|
||||
"""
|
||||
|
||||
from warnings import deprecated
|
||||
from corelibs_strings.string_format import format_bytes as corelibs_format_bytes
|
||||
|
||||
|
||||
@deprecated("Use corelibs_strings.string_format.format_bytes instead")
|
||||
def format_bytes(byte_value: float | int | str) -> str:
|
||||
"""
|
||||
Format a byte value to a human readable string
|
||||
@@ -14,24 +18,9 @@ def format_bytes(byte_value: float | int | str) -> str:
|
||||
str -- _description_
|
||||
"""
|
||||
# if string exit
|
||||
if isinstance(byte_value, str):
|
||||
return byte_value
|
||||
# empty byte value is set to 0
|
||||
if not byte_value:
|
||||
byte_value = float(0)
|
||||
# if not float, convert to flaot
|
||||
if isinstance(byte_value, int):
|
||||
byte_value = float(byte_value)
|
||||
# loop through valid extensions
|
||||
for unit in ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB"]:
|
||||
# never go into the negativ and check if it is smaller than next set
|
||||
# if it is, print out return string
|
||||
if abs(byte_value) < 1024.0:
|
||||
return f"{byte_value:,.2f} {unit}"
|
||||
# divided for the next loop check
|
||||
byte_value /= 1024.0
|
||||
# if it is too big, return YB
|
||||
return f"{byte_value:,.2f} YB"
|
||||
return corelibs_format_bytes(
|
||||
byte_value=byte_value,
|
||||
)
|
||||
|
||||
|
||||
# __NED__
|
||||
|
||||
@@ -2,225 +2,18 @@
|
||||
Format double byte strings to exact length
|
||||
"""
|
||||
|
||||
import unicodedata
|
||||
from warnings import warn
|
||||
from corelibs_double_byte_format.double_byte_string_format import (
|
||||
DoubleByteFormatString as CorelibsDoubleByteFormatString
|
||||
)
|
||||
|
||||
|
||||
class DoubleByteFormatString:
|
||||
class DoubleByteFormatString(CorelibsDoubleByteFormatString):
|
||||
"""
|
||||
Format a string to exact length
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
string: str,
|
||||
cut_length: int,
|
||||
format_length: int | None = None,
|
||||
placeholder: str = '..',
|
||||
format_string: str = '{{:<{len}}}'
|
||||
):
|
||||
"""
|
||||
shorts a string to exact cut length and sets it to format length
|
||||
|
||||
after "cut_length" cut the "placeholder" will be added, so that the new cut_length is never
|
||||
larget than the cut_length given (".." is counted to cut_length)
|
||||
if format_length if set and outside format_length will be set
|
||||
the cut_length is adjusted to format_length if the format_length is shorter
|
||||
|
||||
Example
|
||||
|
||||
"Foo bar baz" 10 charcters -> 5 cut_length -> 10 format_length
|
||||
"Foo.. "
|
||||
|
||||
use class.get_string_short() for cut length shortend string
|
||||
use class.get_string_short_formated() to get the shorted string to format length padding
|
||||
|
||||
creates a class that shortens and sets the format length
|
||||
to use with a print format run the format needs to be pre set in
|
||||
the style of {{:<{len}}} style
|
||||
self.get_string_short_formated() for the "len" parameter
|
||||
|
||||
Args:
|
||||
string (str): string to work with
|
||||
cut_length (int): width to shorten to
|
||||
format_length (int | None): format length. Defaults to None
|
||||
placeholder (str, optional): placeholder to put after shortened string. Defaults to '..'.
|
||||
format_string (str, optional): format string. Defaults to '{{:<{len}}}'
|
||||
"""
|
||||
# output variables
|
||||
self.string_short: str = ''
|
||||
self.string_width_value: int = 0
|
||||
self.string_short_width: int = 0
|
||||
self.format_length_value: int = 0
|
||||
# internal varaibles
|
||||
self.placeholder: str = placeholder
|
||||
# original string
|
||||
self.string: str = ''
|
||||
# width to cut string to
|
||||
self.cut_length: int = 0
|
||||
# format length to set to
|
||||
self.format_length: int = 0
|
||||
# main string
|
||||
self.string = str(string)
|
||||
|
||||
self.format_string: str = format_string
|
||||
|
||||
# if width is > 0 set, else set width of string (fallback)
|
||||
if cut_length > 0:
|
||||
self.cut_length = cut_length
|
||||
elif cut_length <= 0:
|
||||
self.cut_length = self.__string_width_calc(self.string)
|
||||
# format length set, if not set or smaller than 0, set to width of string
|
||||
self.format_length = self.cut_length
|
||||
if format_length is not None and format_length > 0:
|
||||
self.format_length = format_length
|
||||
# check that width is not larger then length if yes, set width to length
|
||||
self.cut_length = min(self.cut_length, self.format_length)
|
||||
|
||||
# process the string shorten and format length calculation
|
||||
self.process()
|
||||
|
||||
def process(self):
|
||||
"""
|
||||
runs all the class methods to set string length, the string shortened
|
||||
and the format length
|
||||
"""
|
||||
# call the internal ones to set the data
|
||||
if self.string:
|
||||
self.__string_width()
|
||||
self.__shorten_string()
|
||||
if self.format_length:
|
||||
self.__format_length()
|
||||
|
||||
def get_string_short(self) -> str:
|
||||
"""
|
||||
get the shortend string
|
||||
|
||||
Returns:
|
||||
str -- _description_
|
||||
"""
|
||||
return self.string_short
|
||||
|
||||
def get_string_short_formated(self, format_string: str = '{{:<{len}}}') -> str:
|
||||
"""
|
||||
get the formatted string
|
||||
|
||||
Keyword Arguments:
|
||||
format_string {_type_} -- _description_ (default: {'{{:<{len}}}'})
|
||||
|
||||
Returns:
|
||||
str -- _description_
|
||||
"""
|
||||
if not format_string:
|
||||
format_string = self.format_string
|
||||
return format_string.format(
|
||||
len=self.get_format_length()
|
||||
).format(
|
||||
self.get_string_short()
|
||||
)
|
||||
|
||||
def get_format_length(self) -> int:
|
||||
"""
|
||||
get the format length for outside length set
|
||||
|
||||
Returns:
|
||||
int -- _description_
|
||||
"""
|
||||
return self.format_length_value
|
||||
|
||||
def get_cut_length(self) -> int:
|
||||
"""
|
||||
get the actual cut length
|
||||
|
||||
Returns:
|
||||
int -- _description_
|
||||
"""
|
||||
return self.cut_length
|
||||
|
||||
def get_requested_cut_length(self) -> int:
|
||||
"""
|
||||
get the requested cut length
|
||||
|
||||
Returns:
|
||||
int -- _description_
|
||||
"""
|
||||
return self.cut_length
|
||||
|
||||
def get_requested_format_length(self) -> int:
|
||||
"""
|
||||
get the requested format length
|
||||
|
||||
Returns:
|
||||
int -- _description_
|
||||
"""
|
||||
return self.format_length
|
||||
|
||||
def __string_width_calc(self, string: str) -> int:
|
||||
"""
|
||||
does the actual string width calculation
|
||||
|
||||
Args:
|
||||
string (str): string to calculate from
|
||||
|
||||
Returns:
|
||||
int: stringth width
|
||||
"""
|
||||
return sum(1 + (unicodedata.east_asian_width(c) in "WF") for c in string)
|
||||
|
||||
def __string_width(self):
|
||||
"""
|
||||
calculates the string width based on the characters
|
||||
this is an internal method and should not be called on itself
|
||||
"""
|
||||
# only run if string is set and is valid string
|
||||
if self.string:
|
||||
# calculate width. add +1 for each double byte character
|
||||
self.string_width_value = self.__string_width_calc(self.string)
|
||||
|
||||
def __format_length(self):
|
||||
"""
|
||||
set the format length based on the length for the format
|
||||
and the shortend string
|
||||
this is an internal method and should not be called on itself
|
||||
"""
|
||||
if not self.string_short:
|
||||
self.__shorten_string()
|
||||
# get correct format length based on string
|
||||
if (
|
||||
self.string_short and
|
||||
self.format_length > 0 and
|
||||
self.string_short_width > 0
|
||||
):
|
||||
# length: format length wanted
|
||||
# substract the width of the shortend string - the length of the shortend string
|
||||
self.format_length_value = self.format_length - (self.string_short_width - len(self.string_short))
|
||||
else:
|
||||
# if we have nothing to shorten the length, keep the old one
|
||||
self.format_length_value = self.format_length
|
||||
|
||||
def __shorten_string(self):
|
||||
"""
|
||||
shorten string down to set width
|
||||
this is an internal method and should not be called on itself
|
||||
"""
|
||||
# set string width if not set
|
||||
if not self.string_width_value:
|
||||
self.__string_width()
|
||||
# if the double byte string width is larger than the wanted width
|
||||
if self.string_width_value > self.cut_length:
|
||||
cur_len = 0
|
||||
self.string_short = ''
|
||||
for char in str(self.string):
|
||||
# set the current length if we add the character
|
||||
cur_len += 2 if unicodedata.east_asian_width(char) in "WF" else 1
|
||||
# if the new length is smaller than the output length to shorten too add the char
|
||||
if cur_len <= (self.cut_length - len(self.placeholder)):
|
||||
self.string_short += char
|
||||
self.string_short_width = cur_len
|
||||
# return string with new width and placeholder
|
||||
self.string_short = f"{self.string_short}{self.placeholder}"
|
||||
self.string_short_width += len(self.placeholder)
|
||||
else:
|
||||
# if string is same saze just copy
|
||||
self.string_short = self.string
|
||||
warn("Use 'corelibs_double_byte_format.double_byte_string_format' instead", DeprecationWarning, stacklevel=2)
|
||||
|
||||
# __END__
|
||||
|
||||
@@ -2,10 +2,11 @@
|
||||
Various hash helpers for strings and things
|
||||
"""
|
||||
|
||||
import re
|
||||
import hashlib
|
||||
from warnings import deprecated
|
||||
from corelibs_hash.string_hash import crc32b_fix as corelibs_crc32b_fix, sha1_short as corelibs_sha1_short
|
||||
|
||||
|
||||
@deprecated("Use corelibs_hash.string_hash.crc32b_fix instead")
|
||||
def crc32b_fix(crc: str) -> str:
|
||||
"""
|
||||
fix a CRC32B with wrong order (from old PHP)
|
||||
@@ -16,15 +17,10 @@ def crc32b_fix(crc: str) -> str:
|
||||
Returns:
|
||||
str -- _description_
|
||||
"""
|
||||
# left pad with 0 to 8 chars
|
||||
crc = ("0" * (8 - len(crc))) + crc
|
||||
# flip two chars (byte hex)
|
||||
crc = re.sub(
|
||||
r"^([a-z0-9]{2})([a-z0-9]{2})([a-z0-9]{2})([a-z0-9]{2})$", r"\4\3\2\1", crc
|
||||
)
|
||||
return crc
|
||||
return corelibs_crc32b_fix(crc)
|
||||
|
||||
|
||||
@deprecated("Use corelibs_hash.string_hash.sha1_short instead")
|
||||
def sha1_short(string: str) -> str:
|
||||
"""
|
||||
Return a 9 character long SHA1 part
|
||||
@@ -35,6 +31,6 @@ def sha1_short(string: str) -> str:
|
||||
Returns:
|
||||
str -- _description_
|
||||
"""
|
||||
return hashlib.sha1(string.encode('utf-8')).hexdigest()[:9]
|
||||
return corelibs_sha1_short(string)
|
||||
|
||||
# __END__
|
||||
|
||||
@@ -2,11 +2,16 @@
|
||||
String helpers
|
||||
"""
|
||||
|
||||
import re
|
||||
from decimal import Decimal, getcontext
|
||||
from textwrap import shorten
|
||||
from warnings import deprecated
|
||||
from corelibs_strings.string_support import (
|
||||
shorten_string as corelibs_shorten_string,
|
||||
left_fill as corelibs_left_fill,
|
||||
prepare_url_slash as corelibs_prepare_url_slash,
|
||||
)
|
||||
from corelibs_strings.string_format import format_number as corelibs_format_number
|
||||
|
||||
|
||||
@deprecated("Use corelibs_strings.string_support.shorten_string instead")
|
||||
def shorten_string(
|
||||
string: str | int | float, length: int, hard_shorten: bool = False, placeholder: str = " [~]"
|
||||
) -> str:
|
||||
@@ -23,25 +28,15 @@ def shorten_string(
|
||||
Returns:
|
||||
str: _description_
|
||||
"""
|
||||
string = str(string)
|
||||
# if placeholder > lenght
|
||||
if len(string) > length:
|
||||
if hard_shorten is True or " " not in string:
|
||||
# hard shorten error
|
||||
if len(placeholder) > length:
|
||||
raise ValueError(f"Cannot shorten string: placeholder {placeholder} is too large for max width")
|
||||
short_string = f"{string[:(length - len(placeholder))]}{placeholder}"
|
||||
else:
|
||||
try:
|
||||
short_string = shorten(string, width=length, placeholder=placeholder)
|
||||
except ValueError as e:
|
||||
raise ValueError(f"Cannot shorten string: {e}") from e
|
||||
else:
|
||||
short_string = string
|
||||
|
||||
return short_string
|
||||
return corelibs_shorten_string(
|
||||
string=string,
|
||||
length=length,
|
||||
hard_shorten=hard_shorten,
|
||||
placeholder=placeholder,
|
||||
)
|
||||
|
||||
|
||||
@deprecated("Use corelibs_strings.string_support.left_fill instead")
|
||||
def left_fill(string: str, width: int, char: str = " ") -> str:
|
||||
"""
|
||||
left fill for a certain length to fill a max size
|
||||
@@ -58,19 +53,14 @@ def left_fill(string: str, width: int, char: str = " ") -> str:
|
||||
Returns:
|
||||
str -- _description_
|
||||
"""
|
||||
# the width needs to be string
|
||||
if width < 0:
|
||||
width = len(string)
|
||||
# char can only be one length long
|
||||
if len(char) != 1:
|
||||
char = " "
|
||||
return (
|
||||
"{:"
|
||||
f"{char}>{width}"
|
||||
"}"
|
||||
).format(string)
|
||||
return corelibs_left_fill(
|
||||
string=string,
|
||||
width=width,
|
||||
char=char,
|
||||
)
|
||||
|
||||
|
||||
@deprecated("Use corelibs_strings.string_format.format_number instead")
|
||||
def format_number(number: float, precision: int = 0) -> str:
|
||||
"""
|
||||
format numbers, current trailing zeros does not work
|
||||
@@ -88,21 +78,13 @@ def format_number(number: float, precision: int = 0) -> str:
|
||||
Returns:
|
||||
str -- _description_
|
||||
"""
|
||||
if precision < 0 or precision > 100:
|
||||
precision = 0
|
||||
if precision > 0:
|
||||
getcontext().prec = precision
|
||||
# make it a string to avoid mangling
|
||||
_number = Decimal(str(number))
|
||||
else:
|
||||
_number = number
|
||||
return (
|
||||
"{:,."
|
||||
f"{str(precision)}"
|
||||
"f}"
|
||||
).format(_number)
|
||||
return corelibs_format_number(
|
||||
number=number,
|
||||
precision=precision,
|
||||
)
|
||||
|
||||
|
||||
@deprecated("Use corelibs_strings.string_support.prepare_url_slash instead")
|
||||
def prepare_url_slash(url: str) -> str:
|
||||
"""
|
||||
if the URL does not start with /, add slash
|
||||
@@ -114,9 +96,8 @@ def prepare_url_slash(url: str) -> str:
|
||||
Returns:
|
||||
str -- _description_
|
||||
"""
|
||||
url = re.sub(r'\/+', '/', url)
|
||||
if not url.startswith("/"):
|
||||
url = "/" + url
|
||||
return url
|
||||
return corelibs_prepare_url_slash(
|
||||
url=url,
|
||||
)
|
||||
|
||||
# __END__
|
||||
|
||||
@@ -1,164 +0,0 @@
|
||||
"""
|
||||
PyTest: string_handling/byte_helpers
|
||||
"""
|
||||
|
||||
from corelibs.string_handling.byte_helpers import format_bytes
|
||||
|
||||
|
||||
class TestFormatBytes:
|
||||
"""Tests for format_bytes function"""
|
||||
|
||||
def test_string_input_returned_unchanged(self):
|
||||
"""Test that string inputs are returned as-is"""
|
||||
result = format_bytes("already formatted")
|
||||
assert result == "already formatted"
|
||||
|
||||
def test_empty_string_returned_unchanged(self):
|
||||
"""Test that empty strings are returned as-is"""
|
||||
result = format_bytes("")
|
||||
assert result == ""
|
||||
|
||||
def test_zero_int(self):
|
||||
"""Test zero integer returns 0 bytes"""
|
||||
result = format_bytes(0)
|
||||
assert result == "0.00 B"
|
||||
|
||||
def test_zero_float(self):
|
||||
"""Test zero float returns 0 bytes"""
|
||||
result = format_bytes(0.0)
|
||||
assert result == "0.00 B"
|
||||
|
||||
def test_none_value(self):
|
||||
"""Test None is treated as 0 bytes"""
|
||||
result = format_bytes(None) # type: ignore[arg-type]
|
||||
assert result == "0.00 B"
|
||||
|
||||
def test_bytes_less_than_1kb(self):
|
||||
"""Test formatting bytes less than 1KB"""
|
||||
result = format_bytes(512)
|
||||
assert result == "512.00 B"
|
||||
|
||||
def test_kilobytes(self):
|
||||
"""Test formatting kilobytes"""
|
||||
result = format_bytes(1024)
|
||||
assert result == "1.00 KB"
|
||||
|
||||
def test_kilobytes_with_decimals(self):
|
||||
"""Test formatting kilobytes with decimal values"""
|
||||
result = format_bytes(1536) # 1.5 KB
|
||||
assert result == "1.50 KB"
|
||||
|
||||
def test_megabytes(self):
|
||||
"""Test formatting megabytes"""
|
||||
result = format_bytes(1048576) # 1 MB
|
||||
assert result == "1.00 MB"
|
||||
|
||||
def test_megabytes_with_decimals(self):
|
||||
"""Test formatting megabytes with decimal values"""
|
||||
result = format_bytes(2621440) # 2.5 MB
|
||||
assert result == "2.50 MB"
|
||||
|
||||
def test_gigabytes(self):
|
||||
"""Test formatting gigabytes"""
|
||||
result = format_bytes(1073741824) # 1 GB
|
||||
assert result == "1.00 GB"
|
||||
|
||||
def test_terabytes(self):
|
||||
"""Test formatting terabytes"""
|
||||
result = format_bytes(1099511627776) # 1 TB
|
||||
assert result == "1.00 TB"
|
||||
|
||||
def test_petabytes(self):
|
||||
"""Test formatting petabytes"""
|
||||
result = format_bytes(1125899906842624) # 1 PB
|
||||
assert result == "1.00 PB"
|
||||
|
||||
def test_exabytes(self):
|
||||
"""Test formatting exabytes"""
|
||||
result = format_bytes(1152921504606846976) # 1 EB
|
||||
assert result == "1.00 EB"
|
||||
|
||||
def test_zettabytes(self):
|
||||
"""Test formatting zettabytes"""
|
||||
result = format_bytes(1180591620717411303424) # 1 ZB
|
||||
assert result == "1.00 ZB"
|
||||
|
||||
def test_yottabytes(self):
|
||||
"""Test formatting yottabytes"""
|
||||
result = format_bytes(1208925819614629174706176) # 1 YB
|
||||
assert result == "1.00 YB"
|
||||
|
||||
def test_negative_bytes(self):
|
||||
"""Test formatting negative byte values"""
|
||||
result = format_bytes(-512)
|
||||
assert result == "-512.00 B"
|
||||
|
||||
def test_negative_kilobytes(self):
|
||||
"""Test formatting negative kilobytes"""
|
||||
result = format_bytes(-1024)
|
||||
assert result == "-1.00 KB"
|
||||
|
||||
def test_negative_megabytes(self):
|
||||
"""Test formatting negative megabytes"""
|
||||
result = format_bytes(-1048576)
|
||||
assert result == "-1.00 MB"
|
||||
|
||||
def test_float_input_bytes(self):
|
||||
"""Test float input for bytes"""
|
||||
result = format_bytes(512.5)
|
||||
assert result == "512.50 B"
|
||||
|
||||
def test_float_input_kilobytes(self):
|
||||
"""Test float input for kilobytes"""
|
||||
result = format_bytes(1536.75)
|
||||
assert result == "1.50 KB"
|
||||
|
||||
def test_large_number_formatting(self):
|
||||
"""Test that large numbers use comma separators"""
|
||||
result = format_bytes(10240) # 10 KB
|
||||
assert result == "10.00 KB"
|
||||
|
||||
def test_very_large_byte_value(self):
|
||||
"""Test very large byte value (beyond ZB)"""
|
||||
result = format_bytes(1208925819614629174706176)
|
||||
assert result == "1.00 YB"
|
||||
|
||||
def test_boundary_1023_bytes(self):
|
||||
"""Test boundary case just below 1KB"""
|
||||
result = format_bytes(1023)
|
||||
assert result == "1,023.00 B"
|
||||
|
||||
def test_boundary_1024_bytes(self):
|
||||
"""Test boundary case at exactly 1KB"""
|
||||
result = format_bytes(1024)
|
||||
assert result == "1.00 KB"
|
||||
|
||||
def test_int_converted_to_float(self):
|
||||
"""Test that integer input is properly converted to float"""
|
||||
result = format_bytes(2048)
|
||||
assert result == "2.00 KB"
|
||||
assert "." in result # Verify decimal point is present
|
||||
|
||||
def test_small_decimal_value(self):
|
||||
"""Test small decimal byte value"""
|
||||
result = format_bytes(0.5)
|
||||
assert result == "0.50 B"
|
||||
|
||||
def test_precision_two_decimals(self):
|
||||
"""Test that result always has two decimal places"""
|
||||
result = format_bytes(1024)
|
||||
assert result == "1.00 KB"
|
||||
assert result.count('.') == 1
|
||||
decimal_part = result.split('.')[1].split()[0]
|
||||
assert len(decimal_part) == 2
|
||||
|
||||
def test_mixed_units_progression(self):
|
||||
"""Test progression through multiple unit levels"""
|
||||
# Start with bytes
|
||||
assert "B" in format_bytes(100)
|
||||
# Move to KB
|
||||
assert "KB" in format_bytes(100 * 1024)
|
||||
# Move to MB
|
||||
assert "MB" in format_bytes(100 * 1024 * 1024)
|
||||
# Move to GB
|
||||
assert "GB" in format_bytes(100 * 1024 * 1024 * 1024)
|
||||
@@ -1,524 +0,0 @@
|
||||
"""
|
||||
PyTest: string_handling/double_byte_string_format
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from corelibs.string_handling.double_byte_string_format import DoubleByteFormatString
|
||||
|
||||
|
||||
class TestDoubleByteFormatStringInit:
|
||||
"""Tests for DoubleByteFormatString initialization"""
|
||||
|
||||
def test_basic_initialization(self):
|
||||
"""Test basic initialization with string and cut_length"""
|
||||
formatter = DoubleByteFormatString("Hello World", 10)
|
||||
assert formatter.string == "Hello World"
|
||||
assert formatter.cut_length == 10
|
||||
assert formatter.format_length == 10
|
||||
assert formatter.placeholder == ".."
|
||||
|
||||
def test_initialization_with_format_length(self):
|
||||
"""Test initialization with both cut_length and format_length"""
|
||||
formatter = DoubleByteFormatString("Hello World", 5, 15)
|
||||
assert formatter.cut_length == 5
|
||||
assert formatter.format_length == 15
|
||||
|
||||
def test_initialization_with_custom_placeholder(self):
|
||||
"""Test initialization with custom placeholder"""
|
||||
formatter = DoubleByteFormatString("Hello World", 10, placeholder="...")
|
||||
assert formatter.placeholder == "..."
|
||||
|
||||
def test_initialization_with_custom_format_string(self):
|
||||
"""Test initialization with custom format string"""
|
||||
formatter = DoubleByteFormatString("Hello", 10, format_string="{{:>{len}}}")
|
||||
assert formatter.format_string == "{{:>{len}}}"
|
||||
|
||||
def test_zero_cut_length_uses_string_width(self):
|
||||
"""Test that zero cut_length defaults to string width"""
|
||||
formatter = DoubleByteFormatString("Hello", 0)
|
||||
assert formatter.cut_length > 0
|
||||
# For ASCII string, width should equal length
|
||||
assert formatter.cut_length == 5
|
||||
|
||||
def test_negative_cut_length_uses_string_width(self):
|
||||
"""Test that negative cut_length defaults to string width"""
|
||||
formatter = DoubleByteFormatString("Hello", -5)
|
||||
assert formatter.cut_length > 0
|
||||
|
||||
def test_cut_length_adjusted_to_format_length(self):
|
||||
"""Test that cut_length is adjusted when larger than format_length"""
|
||||
formatter = DoubleByteFormatString("Hello World", 20, 10)
|
||||
assert formatter.cut_length == 10 # Should be min(20, 10)
|
||||
|
||||
def test_none_format_length(self):
|
||||
"""Test with None format_length"""
|
||||
formatter = DoubleByteFormatString("Hello", 10, None)
|
||||
assert formatter.format_length == 10 # Should default to cut_length
|
||||
|
||||
|
||||
class TestDoubleByteFormatStringWithAscii:
|
||||
"""Tests for ASCII (single-byte) string handling"""
|
||||
|
||||
def test_ascii_no_shortening_needed(self):
|
||||
"""Test ASCII string shorter than cut_length"""
|
||||
formatter = DoubleByteFormatString("Hello", 10)
|
||||
assert formatter.get_string_short() == "Hello"
|
||||
assert formatter.string_short_width == 0 # Not set because no shortening
|
||||
|
||||
def test_ascii_exact_cut_length(self):
|
||||
"""Test ASCII string equal to cut_length"""
|
||||
formatter = DoubleByteFormatString("Hello", 5)
|
||||
assert formatter.get_string_short() == "Hello"
|
||||
|
||||
def test_ascii_shortening_required(self):
|
||||
"""Test ASCII string requiring shortening"""
|
||||
formatter = DoubleByteFormatString("Hello World", 8)
|
||||
result = formatter.get_string_short()
|
||||
assert result == "Hello .."
|
||||
assert len(result) == 8
|
||||
|
||||
def test_ascii_with_custom_placeholder(self):
|
||||
"""Test ASCII shortening with custom placeholder"""
|
||||
formatter = DoubleByteFormatString("Hello World", 8, placeholder="...")
|
||||
result = formatter.get_string_short()
|
||||
assert result.endswith("...")
|
||||
assert len(result) == 8
|
||||
|
||||
def test_ascii_very_short_cut_length(self):
|
||||
"""Test ASCII with very short cut_length"""
|
||||
formatter = DoubleByteFormatString("Hello World", 3)
|
||||
result = formatter.get_string_short()
|
||||
assert result == "H.."
|
||||
assert len(result) == 3
|
||||
|
||||
def test_ascii_format_length_calculation(self):
|
||||
"""Test format_length calculation for ASCII strings"""
|
||||
formatter = DoubleByteFormatString("Hello", 10, 15)
|
||||
# String is not shortened, format_length should be 15
|
||||
assert formatter.get_format_length() == 15
|
||||
|
||||
|
||||
class TestDoubleByteFormatStringWithDoubleByte:
|
||||
"""Tests for double-byte (Asian) character handling"""
|
||||
|
||||
def test_japanese_characters(self):
|
||||
"""Test Japanese string handling"""
|
||||
formatter = DoubleByteFormatString("こんにちは", 10)
|
||||
# Each Japanese character is double-width
|
||||
# "こんにちは" = 5 chars * 2 width = 10 width
|
||||
assert formatter.get_string_short() == "こんにちは"
|
||||
|
||||
def test_japanese_shortening(self):
|
||||
"""Test Japanese string requiring shortening"""
|
||||
formatter = DoubleByteFormatString("こんにちは世界", 8)
|
||||
# Should fit 3 double-width chars (6 width) + placeholder (2 chars)
|
||||
result = formatter.get_string_short()
|
||||
assert result.endswith("..")
|
||||
assert len(result) <= 5 # 3 Japanese chars + 2 placeholder chars
|
||||
|
||||
def test_chinese_characters(self):
|
||||
"""Test Chinese string handling"""
|
||||
formatter = DoubleByteFormatString("你好世界", 8)
|
||||
# 4 Chinese chars = 8 width, should fit exactly
|
||||
assert formatter.get_string_short() == "你好世界"
|
||||
|
||||
def test_chinese_shortening(self):
|
||||
"""Test Chinese string requiring shortening"""
|
||||
formatter = DoubleByteFormatString("你好世界朋友", 8)
|
||||
# Should fit 3 double-width chars (6 width) + placeholder (2 chars)
|
||||
result = formatter.get_string_short()
|
||||
assert result.endswith("..")
|
||||
assert len(result) <= 5
|
||||
|
||||
def test_korean_characters(self):
|
||||
"""Test Korean string handling"""
|
||||
formatter = DoubleByteFormatString("안녕하세요", 10)
|
||||
# Korean characters are also double-width
|
||||
assert formatter.get_string_short() == "안녕하세요"
|
||||
|
||||
def test_mixed_ascii_japanese(self):
|
||||
"""Test mixed ASCII and Japanese characters"""
|
||||
formatter = DoubleByteFormatString("Hello世界", 10)
|
||||
# "Hello" = 5 width, "世界" = 4 width, total = 9 width
|
||||
assert formatter.get_string_short() == "Hello世界"
|
||||
|
||||
def test_mixed_ascii_japanese_shortening(self):
|
||||
"""Test mixed string requiring shortening"""
|
||||
formatter = DoubleByteFormatString("Hello世界Test", 10)
|
||||
# Should shorten to fit within 10 width
|
||||
result = formatter.get_string_short()
|
||||
assert result.endswith("..")
|
||||
# Total visual width should be <= 10
|
||||
|
||||
def test_fullwidth_ascii(self):
|
||||
"""Test fullwidth ASCII characters"""
|
||||
# Fullwidth ASCII characters (U+FF01 to U+FF5E)
|
||||
formatter = DoubleByteFormatString("HELLOworld", 10)
|
||||
result = formatter.get_string_short()
|
||||
assert result.endswith("..")
|
||||
|
||||
|
||||
class TestDoubleByteFormatStringGetters:
|
||||
"""Tests for getter methods"""
|
||||
|
||||
def test_get_string_short(self):
|
||||
"""Test get_string_short method"""
|
||||
formatter = DoubleByteFormatString("Hello World", 8)
|
||||
result = formatter.get_string_short()
|
||||
assert isinstance(result, str)
|
||||
assert result == "Hello .."
|
||||
|
||||
def test_get_format_length(self):
|
||||
"""Test get_format_length method"""
|
||||
formatter = DoubleByteFormatString("Hello", 5, 10)
|
||||
assert formatter.get_format_length() == 10
|
||||
|
||||
def test_get_cut_length(self):
|
||||
"""Test get_cut_length method"""
|
||||
formatter = DoubleByteFormatString("Hello", 8)
|
||||
assert formatter.get_cut_length() == 8
|
||||
|
||||
def test_get_requested_cut_length(self):
|
||||
"""Test get_requested_cut_length method"""
|
||||
formatter = DoubleByteFormatString("Hello", 15)
|
||||
assert formatter.get_requested_cut_length() == 15
|
||||
|
||||
def test_get_requested_format_length(self):
|
||||
"""Test get_requested_format_length method"""
|
||||
formatter = DoubleByteFormatString("Hello", 5, 20)
|
||||
assert formatter.get_requested_format_length() == 20
|
||||
|
||||
def test_get_string_short_formated_default(self):
|
||||
"""Test get_string_short_formated with default format"""
|
||||
formatter = DoubleByteFormatString("Hello", 5, 10)
|
||||
result = formatter.get_string_short_formated()
|
||||
assert isinstance(result, str)
|
||||
assert len(result) == 10 # Should be padded to format_length
|
||||
assert result.startswith("Hello")
|
||||
|
||||
def test_get_string_short_formated_custom(self):
|
||||
"""Test get_string_short_formated with custom format string"""
|
||||
formatter = DoubleByteFormatString("Hello", 5, 10)
|
||||
result = formatter.get_string_short_formated("{{:>{len}}}")
|
||||
assert isinstance(result, str)
|
||||
assert result.endswith("Hello") # Right-aligned
|
||||
|
||||
def test_get_string_short_formated_empty_format_string(self):
|
||||
"""Test get_string_short_formated with empty format string falls back to default"""
|
||||
formatter = DoubleByteFormatString("Hello", 5, 10)
|
||||
result = formatter.get_string_short_formated("")
|
||||
# Should use default format_string from initialization
|
||||
assert isinstance(result, str)
|
||||
|
||||
|
||||
class TestDoubleByteFormatStringFormatting:
|
||||
"""Tests for formatted output"""
|
||||
|
||||
def test_format_with_padding(self):
|
||||
"""Test formatted string with padding"""
|
||||
formatter = DoubleByteFormatString("Hello", 5, 10)
|
||||
result = formatter.get_string_short_formated()
|
||||
assert len(result) == 10
|
||||
assert result == "Hello " # Left-aligned with spaces
|
||||
|
||||
def test_format_shortened_string(self):
|
||||
"""Test formatted shortened string"""
|
||||
formatter = DoubleByteFormatString("Hello World", 8, 12)
|
||||
result = formatter.get_string_short_formated()
|
||||
# Should be "Hello .." padded to 12
|
||||
assert len(result) == 12
|
||||
assert result.startswith("Hello ..")
|
||||
|
||||
def test_format_with_double_byte_chars(self):
|
||||
"""Test formatting with double-byte characters"""
|
||||
formatter = DoubleByteFormatString("日本語", 6, 10)
|
||||
result = formatter.get_string_short_formated()
|
||||
# "日本語" = 3 chars * 2 width = 6 width
|
||||
# Format should account for visual width difference
|
||||
assert isinstance(result, str)
|
||||
|
||||
def test_format_shortened_double_byte(self):
|
||||
"""Test formatting shortened double-byte string"""
|
||||
formatter = DoubleByteFormatString("こんにちは世界", 8, 12)
|
||||
result = formatter.get_string_short_formated()
|
||||
assert isinstance(result, str)
|
||||
# Should be shortened and formatted
|
||||
|
||||
|
||||
class TestDoubleByteFormatStringProcess:
|
||||
"""Tests for process method"""
|
||||
|
||||
def test_process_called_on_init(self):
|
||||
"""Test that process is called during initialization"""
|
||||
formatter = DoubleByteFormatString("Hello World", 8)
|
||||
# process() should have been called, so string_short should be set
|
||||
assert formatter.string_short != ''
|
||||
|
||||
def test_manual_process_call(self):
|
||||
"""Test calling process manually"""
|
||||
formatter = DoubleByteFormatString("Hello World", 8)
|
||||
# Modify internal state
|
||||
formatter.string = "New String"
|
||||
# Call process again
|
||||
formatter.process()
|
||||
# Should recalculate based on new string
|
||||
assert formatter.string_short != ''
|
||||
|
||||
def test_process_with_empty_string(self):
|
||||
"""Test process with empty string"""
|
||||
formatter = DoubleByteFormatString("", 10)
|
||||
formatter.process()
|
||||
# Should handle empty string gracefully
|
||||
assert formatter.string_short == ''
|
||||
|
||||
|
||||
class TestDoubleByteFormatStringEdgeCases:
|
||||
"""Tests for edge cases"""
|
||||
|
||||
def test_empty_string(self):
|
||||
"""Test with empty string"""
|
||||
formatter = DoubleByteFormatString("", 10)
|
||||
assert formatter.get_string_short() == ""
|
||||
|
||||
def test_single_character(self):
|
||||
"""Test with single character"""
|
||||
formatter = DoubleByteFormatString("A", 5)
|
||||
assert formatter.get_string_short() == "A"
|
||||
|
||||
def test_single_double_byte_character(self):
|
||||
"""Test with single double-byte character"""
|
||||
formatter = DoubleByteFormatString("日", 5)
|
||||
assert formatter.get_string_short() == "日"
|
||||
|
||||
def test_placeholder_only_length(self):
|
||||
"""Test when cut_length equals placeholder length"""
|
||||
formatter = DoubleByteFormatString("Hello World", 2)
|
||||
result = formatter.get_string_short()
|
||||
assert result == ".."
|
||||
|
||||
def test_very_long_string(self):
|
||||
"""Test with very long string"""
|
||||
long_string = "A" * 1000
|
||||
formatter = DoubleByteFormatString(long_string, 10)
|
||||
result = formatter.get_string_short()
|
||||
assert len(result) == 10
|
||||
assert result.endswith("..")
|
||||
|
||||
def test_very_long_double_byte_string(self):
|
||||
"""Test with very long double-byte string"""
|
||||
long_string = "あ" * 500
|
||||
formatter = DoubleByteFormatString(long_string, 10)
|
||||
result = formatter.get_string_short()
|
||||
# Should be shortened to fit 10 visual width
|
||||
assert result.endswith("..")
|
||||
|
||||
def test_special_characters(self):
|
||||
"""Test with special characters"""
|
||||
formatter = DoubleByteFormatString("Hello!@#$%^&*()", 10)
|
||||
result = formatter.get_string_short()
|
||||
assert isinstance(result, str)
|
||||
|
||||
def test_newlines_and_tabs(self):
|
||||
"""Test with newlines and tabs"""
|
||||
formatter = DoubleByteFormatString("Hello\nWorld\t!", 10)
|
||||
result = formatter.get_string_short()
|
||||
assert isinstance(result, str)
|
||||
|
||||
def test_unicode_emoji(self):
|
||||
"""Test with Unicode emoji"""
|
||||
formatter = DoubleByteFormatString("Hello 👋 World 🌍", 15)
|
||||
result = formatter.get_string_short()
|
||||
assert isinstance(result, str)
|
||||
|
||||
def test_non_string_input_conversion(self):
|
||||
"""Test that non-string inputs are converted to string"""
|
||||
formatter = DoubleByteFormatString(12345, 10) # type: ignore[arg-type]
|
||||
assert formatter.string == "12345"
|
||||
assert formatter.get_string_short() == "12345"
|
||||
|
||||
def test_none_conversion(self):
|
||||
"""Test None conversion to string"""
|
||||
formatter = DoubleByteFormatString(None, 10) # type: ignore[arg-type]
|
||||
assert formatter.string == "None"
|
||||
|
||||
|
||||
class TestDoubleByteFormatStringWidthCalculation:
|
||||
"""Tests for width calculation accuracy"""
|
||||
|
||||
def test_ascii_width_calculation(self):
|
||||
"""Test width calculation for ASCII"""
|
||||
formatter = DoubleByteFormatString("Hello", 10)
|
||||
formatter.process()
|
||||
# ASCII characters should have width = length
|
||||
assert formatter.string_width_value == 5
|
||||
|
||||
def test_japanese_width_calculation(self):
|
||||
"""Test width calculation for Japanese"""
|
||||
formatter = DoubleByteFormatString("こんにちは", 20)
|
||||
formatter.process()
|
||||
# 5 Japanese characters * 2 width each = 10
|
||||
assert formatter.string_width_value == 10
|
||||
|
||||
def test_mixed_width_calculation(self):
|
||||
"""Test width calculation for mixed characters"""
|
||||
formatter = DoubleByteFormatString("Hello日本", 20)
|
||||
formatter.process()
|
||||
# "Hello" = 5 width, "日本" = 4 width, total = 9
|
||||
assert formatter.string_width_value == 9
|
||||
|
||||
def test_fullwidth_latin_calculation(self):
|
||||
"""Test width calculation for fullwidth Latin characters"""
|
||||
# Fullwidth Latin letters
|
||||
formatter = DoubleByteFormatString("ABC", 10)
|
||||
formatter.process()
|
||||
# 3 fullwidth characters * 2 width each = 6
|
||||
assert formatter.string_width_value == 6
|
||||
|
||||
|
||||
# Parametrized tests
|
||||
@pytest.mark.parametrize("string,cut_length,expected_short", [
|
||||
("Hello", 10, "Hello"),
|
||||
("Hello World", 8, "Hello .."),
|
||||
("Hello World Test", 5, "Hel.."),
|
||||
("", 5, ""),
|
||||
("A", 5, "A"),
|
||||
])
|
||||
def test_ascii_shortening_parametrized(string: str, cut_length: int, expected_short: str):
|
||||
"""Parametrized test for ASCII string shortening"""
|
||||
formatter = DoubleByteFormatString(string, cut_length)
|
||||
assert formatter.get_string_short() == expected_short
|
||||
|
||||
|
||||
@pytest.mark.parametrize("string,cut_length,format_length,expected_format_len", [
|
||||
("Hello", 5, 10, 10),
|
||||
("Hello", 10, 5, 5),
|
||||
("Hello World", 8, 12, 12),
|
||||
])
|
||||
def test_format_length_parametrized(
|
||||
string: str,
|
||||
cut_length: int,
|
||||
format_length: int,
|
||||
expected_format_len: int
|
||||
):
|
||||
"""Parametrized test for format length"""
|
||||
formatter = DoubleByteFormatString(string, cut_length, format_length)
|
||||
assert formatter.get_format_length() == expected_format_len
|
||||
|
||||
|
||||
@pytest.mark.parametrize("string,expected_width", [
|
||||
("Hello", 5),
|
||||
("こんにちは", 10), # 5 Japanese chars * 2
|
||||
("Hello日本", 9), # 5 + 4
|
||||
("", 0),
|
||||
("A", 1),
|
||||
("日", 2),
|
||||
])
|
||||
def test_width_calculation_parametrized(string: str, expected_width: int):
|
||||
"""Parametrized test for width calculation"""
|
||||
formatter = DoubleByteFormatString(string, 100) # Large cut_length to avoid shortening
|
||||
formatter.process()
|
||||
if string:
|
||||
assert formatter.string_width_value == expected_width
|
||||
else:
|
||||
assert formatter.string_width_value == 0
|
||||
|
||||
|
||||
@pytest.mark.parametrize("placeholder", [
|
||||
"..",
|
||||
"...",
|
||||
"—",
|
||||
">>>",
|
||||
"~",
|
||||
])
|
||||
def test_custom_placeholder_parametrized(placeholder: str):
|
||||
"""Parametrized test for custom placeholders"""
|
||||
formatter = DoubleByteFormatString("Hello World Test", 8, placeholder=placeholder)
|
||||
result = formatter.get_string_short()
|
||||
assert result.endswith(placeholder)
|
||||
assert len(result) == 8
|
||||
|
||||
|
||||
class TestDoubleByteFormatStringIntegration:
|
||||
"""Integration tests for complete workflows"""
|
||||
|
||||
def test_complete_workflow_ascii(self):
|
||||
"""Test complete workflow with ASCII string"""
|
||||
formatter = DoubleByteFormatString("Hello World", 8, 12)
|
||||
short = formatter.get_string_short()
|
||||
formatted = formatter.get_string_short_formated()
|
||||
|
||||
assert short == "Hello .."
|
||||
assert len(formatted) == 12
|
||||
assert formatted.startswith("Hello ..")
|
||||
|
||||
def test_complete_workflow_japanese(self):
|
||||
"""Test complete workflow with Japanese string"""
|
||||
formatter = DoubleByteFormatString("こんにちは世界", 8, 12)
|
||||
short = formatter.get_string_short()
|
||||
formatted = formatter.get_string_short_formated()
|
||||
|
||||
assert short.endswith("..")
|
||||
assert isinstance(formatted, str)
|
||||
|
||||
def test_complete_workflow_mixed(self):
|
||||
"""Test complete workflow with mixed characters"""
|
||||
formatter = DoubleByteFormatString("Hello世界World", 10, 15)
|
||||
short = formatter.get_string_short()
|
||||
formatted = formatter.get_string_short_formated()
|
||||
|
||||
assert short.endswith("..")
|
||||
assert isinstance(formatted, str)
|
||||
|
||||
def test_table_like_output(self):
|
||||
"""Test creating table-like output with multiple formatters"""
|
||||
items = [
|
||||
("Name", "Alice", 10, 15),
|
||||
("City", "Tokyo東京", 10, 15),
|
||||
("Country", "Japan日本国", 10, 15),
|
||||
]
|
||||
|
||||
results: list[str] = []
|
||||
for _label, value, cut, fmt in items:
|
||||
formatter = DoubleByteFormatString(value, cut, fmt)
|
||||
results.append(formatter.get_string_short_formated())
|
||||
|
||||
# All results should be formatted strings
|
||||
# Note: Due to double-byte character width adjustments,
|
||||
# the actual string length may differ from format_length
|
||||
assert all(isinstance(result, str) for result in results)
|
||||
assert all(len(result) > 0 for result in results)
|
||||
|
||||
def test_reprocess_after_modification(self):
|
||||
"""Test reprocessing after modifying formatter properties"""
|
||||
formatter = DoubleByteFormatString("Hello World", 8, 12)
|
||||
initial = formatter.get_string_short()
|
||||
|
||||
# Modify and reprocess
|
||||
formatter.string = "New String Test"
|
||||
formatter.process()
|
||||
modified = formatter.get_string_short()
|
||||
|
||||
assert initial != modified
|
||||
assert modified.endswith("..")
|
||||
|
||||
|
||||
class TestDoubleByteFormatStringRightAlignment:
|
||||
"""Tests for right-aligned formatting"""
|
||||
|
||||
def test_right_aligned_format(self):
|
||||
"""Test right-aligned formatting"""
|
||||
formatter = DoubleByteFormatString("Hello", 5, 10, format_string="{{:>{len}}}")
|
||||
result = formatter.get_string_short_formated()
|
||||
assert len(result) == 10
|
||||
# The format applies to the short string
|
||||
assert "Hello" in result
|
||||
|
||||
def test_center_aligned_format(self):
|
||||
"""Test center-aligned formatting"""
|
||||
formatter = DoubleByteFormatString("Hello", 5, 11, format_string="{{:^{len}}}")
|
||||
result = formatter.get_string_short_formated()
|
||||
assert len(result) == 11
|
||||
assert "Hello" in result
|
||||
|
||||
|
||||
# __END__
|
||||
@@ -1,328 +0,0 @@
|
||||
"""
|
||||
PyTest: string_handling/hash_helpers
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from corelibs.string_handling.hash_helpers import (
|
||||
crc32b_fix, sha1_short
|
||||
)
|
||||
|
||||
|
||||
class TestCrc32bFix:
|
||||
"""Tests for crc32b_fix function"""
|
||||
|
||||
def test_basic_crc_fix(self):
|
||||
"""Test basic CRC32B byte order fix"""
|
||||
# Example: if input is "abcdefgh", it should become "ghefcdab"
|
||||
result = crc32b_fix("abcdefgh")
|
||||
assert result == "ghefcdab"
|
||||
|
||||
def test_short_crc_padding(self):
|
||||
"""Test that short CRC is left-padded with zeros"""
|
||||
# Input with 6 chars should be padded to 8: "00abcdef"
|
||||
# Split into pairs: "00", "ab", "cd", "ef"
|
||||
# Reversed: "ef", "cd", "ab", "00"
|
||||
result = crc32b_fix("abcdef")
|
||||
assert result == "efcdab00"
|
||||
assert len(result) == 8
|
||||
|
||||
def test_4_char_crc(self):
|
||||
"""Test CRC with 4 characters"""
|
||||
# Padded: "0000abcd"
|
||||
# Pairs: "00", "00", "ab", "cd"
|
||||
# Reversed: "cd", "ab", "00", "00"
|
||||
result = crc32b_fix("abcd")
|
||||
assert result == "cdab0000"
|
||||
assert len(result) == 8
|
||||
|
||||
def test_2_char_crc(self):
|
||||
"""Test CRC with 2 characters"""
|
||||
# Padded: "000000ab"
|
||||
# Pairs: "00", "00", "00", "ab"
|
||||
# Reversed: "ab", "00", "00", "00"
|
||||
result = crc32b_fix("ab")
|
||||
assert result == "ab000000"
|
||||
assert len(result) == 8
|
||||
|
||||
def test_1_char_crc(self):
|
||||
"""Test CRC with 1 character"""
|
||||
# Padded: "0000000a"
|
||||
# Pairs: "00", "00", "00", "0a"
|
||||
# Reversed: "0a", "00", "00", "00"
|
||||
result = crc32b_fix("a")
|
||||
assert result == "0a000000"
|
||||
assert len(result) == 8
|
||||
|
||||
def test_empty_crc(self):
|
||||
"""Test empty CRC string"""
|
||||
result = crc32b_fix("")
|
||||
assert result == "00000000"
|
||||
assert len(result) == 8
|
||||
|
||||
def test_numeric_crc(self):
|
||||
"""Test CRC with numeric characters"""
|
||||
result = crc32b_fix("12345678")
|
||||
assert result == "78563412"
|
||||
|
||||
def test_mixed_alphanumeric(self):
|
||||
"""Test CRC with mixed alphanumeric characters"""
|
||||
result = crc32b_fix("a1b2c3d4")
|
||||
assert result == "d4c3b2a1"
|
||||
|
||||
def test_lowercase_letters(self):
|
||||
"""Test CRC with lowercase letters"""
|
||||
result = crc32b_fix("aabbccdd")
|
||||
assert result == "ddccbbaa"
|
||||
|
||||
def test_with_numbers_and_letters(self):
|
||||
"""Test CRC with numbers and letters (typical hex)"""
|
||||
result = crc32b_fix("1a2b3c4d")
|
||||
assert result == "4d3c2b1a"
|
||||
|
||||
def test_all_zeros(self):
|
||||
"""Test CRC with all zeros"""
|
||||
result = crc32b_fix("00000000")
|
||||
assert result == "00000000"
|
||||
|
||||
def test_short_padding_all_numbers(self):
|
||||
"""Test padding with all numbers"""
|
||||
# Padded: "00123456"
|
||||
# Pairs: "00", "12", "34", "56"
|
||||
# Reversed: "56", "34", "12", "00"
|
||||
result = crc32b_fix("123456")
|
||||
assert result == "56341200"
|
||||
assert len(result) == 8
|
||||
|
||||
def test_typical_hex_values(self):
|
||||
"""Test with typical hexadecimal hash values"""
|
||||
result = crc32b_fix("a1b2c3d4")
|
||||
assert result == "d4c3b2a1"
|
||||
|
||||
def test_7_char_crc(self):
|
||||
"""Test CRC with 7 characters (needs 1 zero padding)"""
|
||||
# Padded: "0abcdefg"
|
||||
# Pairs: "0a", "bc", "de", "fg"
|
||||
# Reversed: "fg", "de", "bc", "0a"
|
||||
result = crc32b_fix("abcdefg")
|
||||
assert result == "fgdebc0a"
|
||||
assert len(result) == 8
|
||||
|
||||
|
||||
class TestSha1Short:
|
||||
"""Tests for sha1_short function"""
|
||||
|
||||
def test_basic_sha1_short(self):
|
||||
"""Test basic SHA1 short hash generation"""
|
||||
result = sha1_short("hello")
|
||||
assert len(result) == 9
|
||||
assert result.isalnum() # Should be hexadecimal
|
||||
|
||||
def test_consistent_output(self):
|
||||
"""Test that same input produces same output"""
|
||||
result1 = sha1_short("test")
|
||||
result2 = sha1_short("test")
|
||||
assert result1 == result2
|
||||
|
||||
def test_different_inputs_different_outputs(self):
|
||||
"""Test that different inputs produce different outputs"""
|
||||
result1 = sha1_short("hello")
|
||||
result2 = sha1_short("world")
|
||||
assert result1 != result2
|
||||
|
||||
def test_empty_string(self):
|
||||
"""Test SHA1 of empty string"""
|
||||
result = sha1_short("")
|
||||
assert len(result) == 9
|
||||
# SHA1 of empty string is known: "da39a3ee5e6b4b0d3255bfef95601890afd80709"
|
||||
assert result == "da39a3ee5"
|
||||
|
||||
def test_single_character(self):
|
||||
"""Test SHA1 of single character"""
|
||||
result = sha1_short("a")
|
||||
assert len(result) == 9
|
||||
# SHA1 of "a" is "86f7e437faa5a7fce15d1ddcb9eaeaea377667b8"
|
||||
assert result == "86f7e437f"
|
||||
|
||||
def test_long_string(self):
|
||||
"""Test SHA1 of long string"""
|
||||
long_string = "a" * 1000
|
||||
result = sha1_short(long_string)
|
||||
assert len(result) == 9
|
||||
assert result.isalnum()
|
||||
|
||||
def test_special_characters(self):
|
||||
"""Test SHA1 with special characters"""
|
||||
result = sha1_short("hello@world!")
|
||||
assert len(result) == 9
|
||||
assert result.isalnum()
|
||||
|
||||
def test_unicode_characters(self):
|
||||
"""Test SHA1 with unicode characters"""
|
||||
result = sha1_short("こんにちは")
|
||||
assert len(result) == 9
|
||||
assert result.isalnum()
|
||||
|
||||
def test_numbers(self):
|
||||
"""Test SHA1 with numeric string"""
|
||||
result = sha1_short("12345")
|
||||
assert len(result) == 9
|
||||
assert result.isalnum()
|
||||
|
||||
def test_whitespace(self):
|
||||
"""Test SHA1 with whitespace"""
|
||||
result1 = sha1_short("hello world")
|
||||
result2 = sha1_short("helloworld")
|
||||
assert result1 != result2
|
||||
assert len(result1) == 9
|
||||
assert len(result2) == 9
|
||||
|
||||
def test_newlines_and_tabs(self):
|
||||
"""Test SHA1 with newlines and tabs"""
|
||||
result = sha1_short("hello\nworld\ttab")
|
||||
assert len(result) == 9
|
||||
assert result.isalnum()
|
||||
|
||||
def test_mixed_case(self):
|
||||
"""Test SHA1 with mixed case (should be case sensitive)"""
|
||||
result1 = sha1_short("Hello")
|
||||
result2 = sha1_short("hello")
|
||||
assert result1 != result2
|
||||
|
||||
def test_hexadecimal_output(self):
|
||||
"""Test that output is valid hexadecimal"""
|
||||
result = sha1_short("test")
|
||||
# Should only contain 0-9 and a-f
|
||||
assert all(c in "0123456789abcdef" for c in result)
|
||||
|
||||
def test_known_value_verification(self):
|
||||
"""Test against known SHA1 values"""
|
||||
# SHA1 of "hello" is "aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d"
|
||||
result = sha1_short("hello")
|
||||
assert result == "aaf4c61dd"
|
||||
|
||||
def test_numeric_string_input(self):
|
||||
"""Test with numeric string"""
|
||||
result = sha1_short("123456789")
|
||||
assert len(result) == 9
|
||||
assert result.isalnum()
|
||||
|
||||
def test_emoji_input(self):
|
||||
"""Test with emoji characters"""
|
||||
result = sha1_short("😀🎉")
|
||||
assert len(result) == 9
|
||||
assert result.isalnum()
|
||||
|
||||
def test_multiline_string(self):
|
||||
"""Test with multiline string"""
|
||||
multiline = """This is
|
||||
a multiline
|
||||
string"""
|
||||
result = sha1_short(multiline)
|
||||
assert len(result) == 9
|
||||
assert result.isalnum()
|
||||
|
||||
|
||||
# Parametrized tests
|
||||
@pytest.mark.parametrize("input_crc,expected", [
|
||||
("abcdefgh", "ghefcdab"),
|
||||
("12345678", "78563412"),
|
||||
("aabbccdd", "ddccbbaa"),
|
||||
("00000000", "00000000"),
|
||||
("", "00000000"),
|
||||
("a", "0a000000"),
|
||||
("ab", "ab000000"),
|
||||
("abcd", "cdab0000"),
|
||||
("abcdef", "efcdab00"),
|
||||
])
|
||||
def test_crc32b_fix_parametrized(input_crc: str, expected: str):
|
||||
"""Parametrized test for crc32b_fix"""
|
||||
result = crc32b_fix(input_crc)
|
||||
assert len(result) == 8
|
||||
assert result == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize("input_string,expected_length", [
|
||||
("hello", 9),
|
||||
("world", 9),
|
||||
("", 9),
|
||||
("a" * 1000, 9),
|
||||
("test123", 9),
|
||||
("😀", 9),
|
||||
])
|
||||
def test_sha1_short_parametrized_length(input_string: str, expected_length: int):
|
||||
"""Parametrized test for sha1_short to verify consistent length"""
|
||||
result = sha1_short(input_string)
|
||||
assert len(result) == expected_length
|
||||
|
||||
|
||||
@pytest.mark.parametrize("input_string,expected_hash", [
|
||||
("", "da39a3ee5"),
|
||||
("a", "86f7e437f"),
|
||||
("hello", "aaf4c61dd"),
|
||||
("world", "7c211433f"),
|
||||
("test", "a94a8fe5c"),
|
||||
])
|
||||
def test_sha1_short_known_values(input_string: str, expected_hash: str):
|
||||
"""Parametrized test for sha1_short with known SHA1 values"""
|
||||
result = sha1_short(input_string)
|
||||
assert result == expected_hash
|
||||
|
||||
|
||||
# Edge case tests
|
||||
class TestEdgeCases:
|
||||
"""Test edge cases for hash helper functions"""
|
||||
|
||||
def test_crc32b_fix_with_max_length(self):
|
||||
"""Test crc32b_fix with exactly 8 characters"""
|
||||
result = crc32b_fix("ffffffff")
|
||||
assert result == "ffffffff"
|
||||
assert len(result) == 8
|
||||
|
||||
def test_sha1_short_very_long_input(self):
|
||||
"""Test sha1_short with very long input"""
|
||||
very_long = "x" * 10000
|
||||
result = sha1_short(very_long)
|
||||
assert len(result) == 9
|
||||
assert result.isalnum()
|
||||
|
||||
def test_sha1_short_binary_like_string(self):
|
||||
"""Test sha1_short with binary-like string"""
|
||||
result = sha1_short("\x00\x01\x02\x03")
|
||||
assert len(result) == 9
|
||||
assert result.isalnum()
|
||||
|
||||
def test_crc32b_fix_preserves_characters(self):
|
||||
"""Test that crc32b_fix only reorders, doesn't change characters"""
|
||||
input_crc = "12345678"
|
||||
result = crc32b_fix(input_crc)
|
||||
# All characters from input should be in output (after padding)
|
||||
for char in input_crc:
|
||||
assert char in result or '0' in result # 0 is for padding
|
||||
|
||||
|
||||
# Integration tests
|
||||
class TestIntegration:
|
||||
"""Integration tests for hash helper functions"""
|
||||
|
||||
def test_sha1_short_produces_valid_crc_input(self):
|
||||
"""Test that sha1_short output could be used as CRC input"""
|
||||
sha1_result = sha1_short("test")
|
||||
# SHA1 short is 9 chars, CRC expects up to 8, so take first 8
|
||||
crc_input = sha1_result[:8]
|
||||
crc_result = crc32b_fix(crc_input)
|
||||
assert len(crc_result) == 8
|
||||
|
||||
def test_multiple_sha1_short_consistency(self):
|
||||
"""Test that multiple calls to sha1_short are consistent"""
|
||||
results = [sha1_short("consistency_test") for _ in range(10)]
|
||||
assert all(r == results[0] for r in results)
|
||||
|
||||
def test_crc32b_fix_reversibility_concept(self):
|
||||
"""Test that applying crc32b_fix twice reverses the operation"""
|
||||
original = "abcdefgh"
|
||||
fixed_once = crc32b_fix(original)
|
||||
fixed_twice = crc32b_fix(fixed_once)
|
||||
assert fixed_twice == original
|
||||
|
||||
|
||||
# __END__
|
||||
@@ -1,327 +0,0 @@
|
||||
"""
|
||||
PyTest: string_handling/string_helpers
|
||||
"""
|
||||
|
||||
from textwrap import shorten
|
||||
import pytest
|
||||
from corelibs.string_handling.string_helpers import (
|
||||
shorten_string, left_fill, format_number, prepare_url_slash
|
||||
)
|
||||
|
||||
|
||||
class TestShortenString:
|
||||
"""Tests for shorten_string function"""
|
||||
|
||||
def test_string_shorter_than_length(self):
|
||||
"""Test that strings shorter than length are returned unchanged"""
|
||||
result = shorten_string("hello", 10)
|
||||
assert result == "hello"
|
||||
|
||||
def test_string_equal_to_length(self):
|
||||
"""Test that strings equal to length are returned unchanged"""
|
||||
result = shorten_string("hello", 5)
|
||||
assert result == "hello"
|
||||
|
||||
def test_hard_shorten_true(self):
|
||||
"""Test hard shortening with default placeholder"""
|
||||
result = shorten_string("hello world", 8, hard_shorten=True)
|
||||
assert result == "hell [~]"
|
||||
|
||||
def test_hard_shorten_custom_placeholder(self):
|
||||
"""Test hard shortening with custom placeholder"""
|
||||
result = shorten_string("hello world", 8, hard_shorten=True, placeholder="...")
|
||||
assert result == "hello..."
|
||||
|
||||
def test_no_spaces_auto_hard_shorten(self):
|
||||
"""Test that strings without spaces automatically use hard shorten"""
|
||||
result = shorten_string("helloworld", 8)
|
||||
assert result == "hell [~]"
|
||||
|
||||
def test_soft_shorten_with_spaces(self):
|
||||
"""Test soft shortening using textwrap.shorten"""
|
||||
result = shorten_string("hello world test", 12)
|
||||
# Should use textwrap.shorten behavior
|
||||
expected = shorten("hello world test", width=12, placeholder=" [~]")
|
||||
assert result == expected
|
||||
|
||||
def test_placeholder_too_large_hard_shorten(self):
|
||||
"""Test error when placeholder is larger than allowed length"""
|
||||
with pytest.raises(ValueError, match="Cannot shorten string: placeholder .* is too large for max width"):
|
||||
shorten_string("hello", 3, hard_shorten=True, placeholder=" [~]")
|
||||
|
||||
def test_placeholder_too_large_no_spaces(self):
|
||||
"""Test error when placeholder is larger than allowed length for string without spaces"""
|
||||
with pytest.raises(ValueError, match="Cannot shorten string: placeholder .* is too large for max width"):
|
||||
shorten_string("hello", 3, placeholder=" [~]")
|
||||
|
||||
def test_textwrap_shorten_error(self):
|
||||
"""Test handling of textwrap.shorten ValueError"""
|
||||
# This might be tricky to trigger, but we can mock it
|
||||
with pytest.raises(ValueError, match="Cannot shorten string:"):
|
||||
# Very short length that might cause textwrap.shorten to fail
|
||||
shorten_string("hello world", 1, hard_shorten=False)
|
||||
|
||||
def test_type_conversion(self):
|
||||
"""Test that inputs are converted to proper types"""
|
||||
result = shorten_string(12345, 8, hard_shorten=True)
|
||||
assert result == "12345"
|
||||
|
||||
def test_empty_string(self):
|
||||
"""Test with empty string"""
|
||||
result = shorten_string("", 5)
|
||||
assert result == ""
|
||||
|
||||
def test_zero_length(self):
|
||||
"""Test with zero length"""
|
||||
with pytest.raises(ValueError):
|
||||
shorten_string("hello", 0, hard_shorten=True)
|
||||
|
||||
|
||||
class TestLeftFill:
|
||||
"""Tests for left_fill function"""
|
||||
|
||||
def test_basic_left_fill(self):
|
||||
"""Test basic left filling with spaces"""
|
||||
result = left_fill("hello", 10)
|
||||
assert result == " hello"
|
||||
assert len(result) == 10
|
||||
|
||||
def test_custom_fill_character(self):
|
||||
"""Test left filling with custom character"""
|
||||
result = left_fill("hello", 10, "0")
|
||||
assert result == "00000hello"
|
||||
|
||||
def test_string_longer_than_width(self):
|
||||
"""Test when string is longer than width"""
|
||||
result = left_fill("hello world", 5)
|
||||
assert result == "hello world" # Should return original string
|
||||
|
||||
def test_string_equal_to_width(self):
|
||||
"""Test when string equals width"""
|
||||
result = left_fill("hello", 5)
|
||||
assert result == "hello"
|
||||
|
||||
def test_negative_width(self):
|
||||
"""Test with negative width"""
|
||||
result = left_fill("hello", -5)
|
||||
assert result == "hello" # Should use string length
|
||||
|
||||
def test_zero_width(self):
|
||||
"""Test with zero width"""
|
||||
result = left_fill("hello", 0)
|
||||
assert result == "hello" # Should return original string
|
||||
|
||||
def test_invalid_fill_character(self):
|
||||
"""Test with invalid fill character (not single char)"""
|
||||
result = left_fill("hello", 10, "abc")
|
||||
assert result == " hello" # Should default to space
|
||||
|
||||
def test_empty_fill_character(self):
|
||||
"""Test with empty fill character"""
|
||||
result = left_fill("hello", 10, "")
|
||||
assert result == " hello" # Should default to space
|
||||
|
||||
def test_empty_string(self):
|
||||
"""Test with empty string"""
|
||||
result = left_fill("", 5)
|
||||
assert result == " "
|
||||
|
||||
|
||||
class TestFormatNumber:
|
||||
"""Tests for format_number function"""
|
||||
|
||||
def test_integer_default_precision(self):
|
||||
"""Test formatting integer with default precision"""
|
||||
result = format_number(1234)
|
||||
assert result == "1,234"
|
||||
|
||||
def test_float_default_precision(self):
|
||||
"""Test formatting float with default precision"""
|
||||
result = format_number(1234.56)
|
||||
assert result == "1,235" # Should round to nearest integer
|
||||
|
||||
def test_with_precision(self):
|
||||
"""Test formatting with specified precision"""
|
||||
result = format_number(1234.5678, 2)
|
||||
assert result == "1,234.57"
|
||||
|
||||
def test_large_number(self):
|
||||
"""Test formatting large number"""
|
||||
result = format_number(1234567.89, 2)
|
||||
assert result == "1,234,567.89"
|
||||
|
||||
def test_zero(self):
|
||||
"""Test formatting zero"""
|
||||
result = format_number(0)
|
||||
assert result == "0"
|
||||
|
||||
def test_negative_number(self):
|
||||
"""Test formatting negative number"""
|
||||
result = format_number(-1234.56, 2)
|
||||
assert result == "-1,234.56"
|
||||
|
||||
def test_negative_precision(self):
|
||||
"""Test with negative precision (should default to 0)"""
|
||||
result = format_number(1234.56, -1)
|
||||
assert result == "1,235"
|
||||
|
||||
def test_excessive_precision(self):
|
||||
"""Test with precision > 100 (should default to 0)"""
|
||||
result = format_number(1234.56, 101)
|
||||
assert result == "1,235"
|
||||
|
||||
def test_precision_boundary_values(self):
|
||||
"""Test precision boundary values"""
|
||||
# Test precision = 0 (should work)
|
||||
result = format_number(1234.56, 0)
|
||||
assert result == "1,235"
|
||||
|
||||
# Test precision = 100 (should work)
|
||||
result = format_number(1234.56, 100)
|
||||
assert "1,234.56" in result # Will have many trailing zeros
|
||||
|
||||
def test_small_decimal(self):
|
||||
"""Test formatting small decimal number"""
|
||||
result = format_number(0.123456, 4)
|
||||
assert result == "0.1235"
|
||||
|
||||
def test_very_small_number(self):
|
||||
"""Test formatting very small number"""
|
||||
result = format_number(0.001, 3)
|
||||
assert result == "0.001"
|
||||
|
||||
|
||||
class TestPrepareUrlSlash:
|
||||
"""Tests for prepare_url_slash function"""
|
||||
|
||||
def test_url_without_leading_slash(self):
|
||||
"""Test that URL without leading slash gets one added"""
|
||||
result = prepare_url_slash("api/users")
|
||||
assert result == "/api/users"
|
||||
|
||||
def test_url_with_leading_slash(self):
|
||||
"""Test that URL with leading slash remains unchanged"""
|
||||
result = prepare_url_slash("/api/users")
|
||||
assert result == "/api/users"
|
||||
|
||||
def test_url_with_double_slashes(self):
|
||||
"""Test that double slashes are reduced to single slash"""
|
||||
result = prepare_url_slash("/api//users")
|
||||
assert result == "/api/users"
|
||||
|
||||
def test_url_with_multiple_slashes(self):
|
||||
"""Test that multiple consecutive slashes are reduced to single slash"""
|
||||
result = prepare_url_slash("api///users////data")
|
||||
assert result == "/api/users/data"
|
||||
|
||||
def test_url_with_leading_double_slash(self):
|
||||
"""Test URL starting with double slash"""
|
||||
result = prepare_url_slash("//api/users")
|
||||
assert result == "/api/users"
|
||||
|
||||
def test_url_without_slash_and_double_slashes(self):
|
||||
"""Test URL without leading slash and containing double slashes"""
|
||||
result = prepare_url_slash("api//users//data")
|
||||
assert result == "/api/users/data"
|
||||
|
||||
def test_single_slash(self):
|
||||
"""Test single slash URL"""
|
||||
result = prepare_url_slash("/")
|
||||
assert result == "/"
|
||||
|
||||
def test_multiple_slashes_only(self):
|
||||
"""Test URL with only multiple slashes"""
|
||||
result = prepare_url_slash("///")
|
||||
assert result == "/"
|
||||
|
||||
def test_empty_string(self):
|
||||
"""Test empty string"""
|
||||
result = prepare_url_slash("")
|
||||
assert result == "/"
|
||||
|
||||
def test_url_with_query_params(self):
|
||||
"""Test URL with query parameters"""
|
||||
result = prepare_url_slash("/api/users?id=1")
|
||||
assert result == "/api/users?id=1"
|
||||
|
||||
def test_url_with_double_slashes_and_query(self):
|
||||
"""Test URL with double slashes and query parameters"""
|
||||
result = prepare_url_slash("api//users?id=1")
|
||||
assert result == "/api/users?id=1"
|
||||
|
||||
def test_complex_url_path(self):
|
||||
"""Test complex URL path with multiple segments"""
|
||||
result = prepare_url_slash("api/v1/users/123/profile")
|
||||
assert result == "/api/v1/users/123/profile"
|
||||
|
||||
def test_complex_url_with_multiple_issues(self):
|
||||
"""Test URL with both missing leading slash and multiple double slashes"""
|
||||
result = prepare_url_slash("api//v1///users//123////profile")
|
||||
assert result == "/api/v1/users/123/profile"
|
||||
|
||||
|
||||
# Additional integration tests
|
||||
class TestIntegration:
|
||||
"""Integration tests combining functions"""
|
||||
|
||||
def test_format_and_fill(self):
|
||||
"""Test formatting a number then left filling"""
|
||||
formatted = format_number(1234.56, 2)
|
||||
result = left_fill(formatted, 15)
|
||||
assert result.endswith("1,234.56")
|
||||
assert len(result) == 15
|
||||
|
||||
def test_format_and_shorten(self):
|
||||
"""Test formatting a large number then shortening"""
|
||||
formatted = format_number(123456789.123, 3)
|
||||
result = shorten_string(formatted, 10)
|
||||
assert len(result) <= 10
|
||||
|
||||
|
||||
# Fixtures for parameterized tests
|
||||
@pytest.mark.parametrize("input_str,length,expected", [
|
||||
("hello", 10, "hello"),
|
||||
("hello world", 5, "h [~]"),
|
||||
("test", 4, "test"),
|
||||
("", 5, ""),
|
||||
])
|
||||
def test_shorten_string_parametrized(input_str: str, length: int, expected: str):
|
||||
"""Parametrized test for shorten_string"""
|
||||
result = shorten_string(input_str, length, hard_shorten=True)
|
||||
if expected.endswith(" [~]"):
|
||||
assert result.endswith(" [~]")
|
||||
assert len(result) == length
|
||||
else:
|
||||
assert result == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize("number,precision,expected", [
|
||||
(1000, 0, "1,000"),
|
||||
(1234.56, 2, "1,234.56"),
|
||||
(0, 1, "0.0"),
|
||||
(-500, 0, "-500"),
|
||||
])
|
||||
def test_format_number_parametrized(number: float | int, precision: int, expected: str):
|
||||
"""Parametrized test for format_number"""
|
||||
assert format_number(number, precision) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize("input_url,expected", [
|
||||
("api/users", "/api/users"),
|
||||
("/api/users", "/api/users"),
|
||||
("api//users", "/api/users"),
|
||||
("/api//users", "/api/users"),
|
||||
("//api/users", "/api/users"),
|
||||
("api///users////data", "/api/users/data"),
|
||||
("/", "/"),
|
||||
("///", "/"),
|
||||
("", "/"),
|
||||
("api/v1/users/123", "/api/v1/users/123"),
|
||||
("/api/users?id=1&name=test", "/api/users?id=1&name=test"),
|
||||
("api//users//123//profile", "/api/users/123/profile"),
|
||||
])
|
||||
def test_prepare_url_slash_parametrized(input_url: str, expected: str):
|
||||
"""Parametrized test for prepare_url_slash"""
|
||||
assert prepare_url_slash(input_url) == expected
|
||||
|
||||
# __END__
|
||||
Reference in New Issue
Block a user