diff --git a/pyproject.toml b/pyproject.toml index e0d3b89..77b3156 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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", diff --git a/src/corelibs/string_handling/byte_helpers.py b/src/corelibs/string_handling/byte_helpers.py index 7d96fdf..538e0f5 100644 --- a/src/corelibs/string_handling/byte_helpers.py +++ b/src/corelibs/string_handling/byte_helpers.py @@ -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__ diff --git a/src/corelibs/string_handling/double_byte_string_format.py b/src/corelibs/string_handling/double_byte_string_format.py index dd8276a..667abf4 100644 --- a/src/corelibs/string_handling/double_byte_string_format.py +++ b/src/corelibs/string_handling/double_byte_string_format.py @@ -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__ diff --git a/src/corelibs/string_handling/hash_helpers.py b/src/corelibs/string_handling/hash_helpers.py index 7f6be38..2d2c69d 100644 --- a/src/corelibs/string_handling/hash_helpers.py +++ b/src/corelibs/string_handling/hash_helpers.py @@ -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__ diff --git a/src/corelibs/string_handling/string_helpers.py b/src/corelibs/string_handling/string_helpers.py index 8fe02a9..26e4015 100644 --- a/src/corelibs/string_handling/string_helpers.py +++ b/src/corelibs/string_handling/string_helpers.py @@ -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__ diff --git a/tests/unit/string_handling/test_byte_helpers.py b/tests/unit/string_handling/test_byte_helpers.py deleted file mode 100644 index 27acc98..0000000 --- a/tests/unit/string_handling/test_byte_helpers.py +++ /dev/null @@ -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) diff --git a/tests/unit/string_handling/test_double_byte_format.py b/tests/unit/string_handling/test_double_byte_format.py deleted file mode 100644 index e7599cc..0000000 --- a/tests/unit/string_handling/test_double_byte_format.py +++ /dev/null @@ -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__ diff --git a/tests/unit/string_handling/test_hash_helpers.py b/tests/unit/string_handling/test_hash_helpers.py deleted file mode 100644 index c999aca..0000000 --- a/tests/unit/string_handling/test_hash_helpers.py +++ /dev/null @@ -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__ diff --git a/tests/unit/string_handling/test_string_helpers.py b/tests/unit/string_handling/test_string_helpers.py deleted file mode 100644 index 55430ab..0000000 --- a/tests/unit/string_handling/test_string_helpers.py +++ /dev/null @@ -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__