import pytest import random import string from unittest.mock import patch, mock_open, MagicMock from Whatsapp_Chat_Exporter.utility import * def test_convert_time_unit(): assert convert_time_unit(0) == "less than a second" assert convert_time_unit(1) == "a second" assert convert_time_unit(10) == "10 seconds" assert convert_time_unit(60) == "1 minute" assert convert_time_unit(61) == "1 minute 1 second" assert convert_time_unit(122) == "2 minutes 2 seconds" assert convert_time_unit(3600) == "1 hour" assert convert_time_unit(3661) == "1 hour 1 minute 1 second" assert convert_time_unit(3720) == "1 hour 2 minutes" assert convert_time_unit(3660) == "1 hour 1 minute" assert convert_time_unit(7263) == "2 hours 1 minute 3 seconds" assert convert_time_unit(86400) == "1 day" assert convert_time_unit(86461) == "1 day 1 minute 1 second" assert convert_time_unit(172805) == "2 days 5 seconds" class TestBytesToReadable: assert bytes_to_readable(0) == "0 B" assert bytes_to_readable(500) == "500 B" assert bytes_to_readable(1024) == "1.0 KB" assert bytes_to_readable(2048) == "2.0 KB" assert bytes_to_readable(1536) == "1.5 KB" assert bytes_to_readable(1024**2) == "1.0 MB" assert bytes_to_readable(5 * 1024**2) == "5.0 MB" assert bytes_to_readable(1024**3) == "1.0 GB" assert bytes_to_readable(1024**4) == "1.0 TB" assert bytes_to_readable(1024**5) == "1.0 PB" assert bytes_to_readable(1024**6) == "1.0 EB" assert bytes_to_readable(1024**7) == "1.0 ZB" assert bytes_to_readable(1024**8) == "1.0 YB" class TestReadableToBytes: def test_conversion(self): assert readable_to_bytes("0B") == 0 assert readable_to_bytes("100B") == 100 assert readable_to_bytes("50 B") == 50 assert readable_to_bytes("1KB") == 1024 assert readable_to_bytes("2.5 KB") == 2560 assert readable_to_bytes("2.0 KB") == 2048 assert readable_to_bytes("1MB") == 1024**2 assert readable_to_bytes("0.5 MB") == 524288 assert readable_to_bytes("1. MB") == 1048576 assert readable_to_bytes("1GB") == 1024**3 assert readable_to_bytes("1.GB") == 1024**3 assert readable_to_bytes("1TB") == 1024**4 assert readable_to_bytes("1PB") == 1024**5 assert readable_to_bytes("1EB") == 1024**6 assert readable_to_bytes("1ZB") == 1024**7 assert readable_to_bytes("1YB") == 1024**8 def test_case_insensitivity(self): assert readable_to_bytes("1kb") == 1024 assert readable_to_bytes("2mB") == 2 * 1024**2 def test_whitespace(self): assert readable_to_bytes(" 10 KB ") == 10 * 1024 assert readable_to_bytes(" 1 MB") == 1024**2 def test_invalid_unit(self): with pytest.raises(ValueError, match="Invalid size format for size_str"): readable_to_bytes("100X") readable_to_bytes("A100") readable_to_bytes("100$$$$$") def test_invalid_number(self): with pytest.raises(ValueError, match="Invalid size format for size_str"): readable_to_bytes("ABC KB") def test_missing_unit(self): assert readable_to_bytes("100") == 100 class TestSanitizeExcept: def test_no_tags(self): html = "This is plain text." assert sanitize_except(html) == Markup("This is plain text.") def test_allowed_br_tag(self): html = "Line 1
Line 2" assert sanitize_except(html) == Markup("Line 1
Line 2") html = "
Line" assert sanitize_except(html) == Markup("
Line") html = "Line
" assert sanitize_except(html) == Markup("Line
") def test_mixed_tags(self): html = "Bold
Italic" assert sanitize_except(html) == Markup( "<b>Bold</b>
<i>Italic</i><img src='evil.gif'><script>alert('XSS')</script>") def test_attribute_stripping(self): html = "
" assert sanitize_except(html) == Markup("
") class TestDetermineDay: def test_same_day(self): timestamp1 = 1678838400 # March 15, 2023 00:00:00 GMT timestamp2 = 1678881600 # March 15, 2023 12:00:00 GMT assert determine_day(timestamp1, timestamp2) is None def test_different_day(self): timestamp1 = 1678886400 # March 15, 2023 00:00:00 GMT timestamp2 = 1678972800 # March 16, 2023 00:00:00 GMT assert determine_day(timestamp1, timestamp2) == datetime(2023, 3, 16).date() def test_crossing_month(self): timestamp1 = 1680220800 # March 31, 2023 00:00:00 GMT timestamp2 = 1680307200 # April 1, 2023 00:00:00 GMT assert determine_day(timestamp1, timestamp2) == datetime(2023, 4, 1).date() def test_crossing_year(self): timestamp1 = 1703980800 # December 31, 2023 00:00:00 GMT timestamp2 = 1704067200 # January 1, 2024 00:00:00 GMT assert determine_day(timestamp1, timestamp2) == datetime(2024, 1, 1).date() class TestGetFileName: def test_valid_contact_phone_number_no_chat_name(self): chat = ChatStore(Device.ANDROID, name=None) filename, name = get_file_name("1234567890@s.whatsapp.net", chat) assert filename == "1234567890" assert name == "1234567890" def test_valid_contact_phone_number_with_chat_name(self): chat = ChatStore(Device.IOS, name="My Chat Group") filename, name = get_file_name("1234567890@s.whatsapp.net", chat) assert filename == "1234567890-My-Chat-Group" assert name == "My Chat Group" def test_valid_contact_exported_chat(self): chat = ChatStore(Device.ANDROID, name="Testing") filename, name = get_file_name("ExportedChat", chat) assert filename == "ExportedChat-Testing" assert name == "Testing" def test_valid_contact_special_ids(self): chat = ChatStore(Device.ANDROID, name="Special Chat") filename_000, name_000 = get_file_name("000000000000000", chat) assert filename_000 == "000000000000000-Special-Chat" assert name_000 == "Special Chat" filename_001, name_001 = get_file_name("000000000000001", chat) assert filename_001 == "000000000000001-Special-Chat" assert name_001 == "Special Chat" def test_unexpected_contact_format(self): chat = ChatStore(Device.ANDROID, name="Some Chat") with pytest.raises(ValueError, match="Unexpected contact format: invalid-contact"): get_file_name("invalid-contact", chat) def test_contact_with_hyphen_and_chat_name(self): chat = ChatStore(Device.ANDROID, name="Another Chat") filename, name = get_file_name("123-456-7890@g.us", chat) assert filename == "Another-Chat" assert name == "Another Chat" def test_contact_with_hyphen_no_chat_name(self): chat = ChatStore(Device.ANDROID, name=None) filename, name = get_file_name("123-456-7890@g.us", chat) assert filename == "123-456-7890" assert name == "123-456-7890" class TestGetCondForEmpty: def test_enable_true(self): condition = get_cond_for_empty(True, "c.jid", "c.broadcast") assert condition == "AND (chat.hidden=0 OR c.jid='status@broadcast' OR c.broadcast>0)" def test_enable_false(self): condition = get_cond_for_empty(False, "other_jid", "other_broadcast") assert condition == "" class TestGetChatCondition: ... class TestGetStatusLocation: @patch('os.path.isdir') @patch('os.path.isfile') @patch('os.mkdir') @patch('urllib.request.urlopen') @patch('builtins.open', new_callable=mock_open) def test_offline_static_set(self, mock_open_file, mock_urlopen, mock_mkdir, mock_isfile, mock_isdir): mock_isdir.return_value = False mock_isfile.return_value = False mock_response = MagicMock() mock_response.read.return_value = b'W3.CSS Content' mock_urlopen.return_value.__enter__.return_value = mock_response output_folder = "output_folder" offline_static = "offline_static" result = get_status_location(output_folder, offline_static) assert result == os.path.join(offline_static, "w3.css") mock_mkdir.assert_called_once_with(os.path.join(output_folder, offline_static)) mock_urlopen.assert_called_once_with("https://www.w3schools.com/w3css/4/w3.css") mock_open_file.assert_called_once_with(os.path.join(output_folder, offline_static, "w3.css"), "wb") mock_open_file().write.assert_called_once_with(b'W3.CSS Content') def test_offline_static_not_set(self): result = get_status_location("output_folder", "") assert result == "https://www.w3schools.com/w3css/4/w3.css" class TestSafeName: def generate_random_string(length=50): random.seed(10) return ''.join(random.choice(string.ascii_letters + string.digits + "äöüß") for _ in range(length)) safe_name_test_cases = [ ("This is a test string", "This-is-a-test-string"), ("This is a test string with special characters!@#$%^&*()", "This-is-a-test-string-with-special-characters"), ("This is a test string with numbers 1234567890", "This-is-a-test-string-with-numbers-1234567890"), ("This is a test string with mixed case ThisIsATestString", "This-is-a-test-string-with-mixed-case-ThisIsATestString"), ("This is a test string with extra spaces \u00A0 \u00A0 \u00A0 ThisIsATestString", "This-is-a-test-string-with-extra-spaces-ThisIsATestString"), ("This is a test string with unicode characters äöüß", "This-is-a-test-string-with-unicode-characters-äöüß"), ("這是一個包含中文的測試字符串", "這是一個包含中文的測試字符串"), # Chinese characters, should stay as is ( f"This is a test string with long length {generate_random_string(1000)}", f"This-is-a-test-string-with-long-length-{generate_random_string(1000)}", ), ("", ""), # Empty string (" ", ""), # String with only space ("---", "---"), # String with only hyphens ("___", "___"), # String with only underscores ("a" * 100, "a" * 100), # Long string with single character ("a-b-c-d-e", "a-b-c-d-e"), # String with hyphen ("a_b_c_d_e", "a_b_c_d_e"), # String with underscore ("a b c d e", "a-b-c-d-e"), # String with spaces ("test.com/path/to/resource?param1=value1¶m2=value2", "test.compathtoresourceparam1value1param2value2"), # Test with URL ("filename.txt", "filename.txt"), # Test with filename ("Αυτή είναι μια δοκιμαστική συμβολοσειρά με ελληνικούς χαρακτήρες.", "Αυτή-είναι-μια-δοκιμαστική-συμβολοσειρά-με-ελληνικούς-χαρακτήρες."), # Greek characters ("This is a test with комбинированные знаки ̆ example", "This-is-a-test-with-комбинированные-знаки-example") # Mixed with unicode ] @pytest.mark.parametrize("input_text, expected_output", safe_name_test_cases) def test_safe_name(self, input_text, expected_output): result = safe_name(input_text) assert result == expected_output class TestGetChatCondition: def test_no_filter(self): """Test when filter is None""" result = get_chat_condition(None, True, ["column1", "column2"]) assert result == "" result = get_chat_condition(None, False, ["column1"]) assert result == "" def test_include_single_chat_single_column(self): """Test including a single chat with single column""" result = get_chat_condition(["1234567890"], True, ["phone"]) assert result == "AND ( phone LIKE '%1234567890%')" def test_include_multiple_chats_single_column(self): """Test including multiple chats with single column""" result = get_chat_condition(["1234567890", "0987654321"], True, ["phone"]) assert result == "AND ( phone LIKE '%1234567890%' OR phone LIKE '%0987654321%')" def test_exclude_single_chat_single_column(self): """Test excluding a single chat with single column""" result = get_chat_condition(["1234567890"], False, ["phone"]) assert result == "AND ( phone NOT LIKE '%1234567890%')" def test_exclude_multiple_chats_single_column(self): """Test excluding multiple chats with single column""" result = get_chat_condition(["1234567890", "0987654321"], False, ["phone"]) assert result == "AND ( phone NOT LIKE '%1234567890%' AND phone NOT LIKE '%0987654321%')" def test_include_with_jid_android(self): """Test including chats with JID for Android platform""" result = get_chat_condition(["1234567890"], True, ["phone", "name"], "jid", "android") assert result == "AND ( phone LIKE '%1234567890%' OR (name LIKE '%1234567890%' AND jid.type == 1))" def test_include_with_jid_ios(self): """Test including chats with JID for iOS platform""" result = get_chat_condition(["1234567890"], True, ["phone", "name"], "jid", "ios") assert result == "AND ( phone LIKE '%1234567890%' OR (name LIKE '%1234567890%' AND jid IS NOT NULL))" def test_exclude_with_jid_android(self): """Test excluding chats with JID for Android platform""" result = get_chat_condition(["1234567890"], False, ["phone", "name"], "jid", "android") assert result == "AND ( phone NOT LIKE '%1234567890%' AND (name NOT LIKE '%1234567890%' AND jid.type == 1))" def test_exclude_with_jid_ios(self): """Test excluding chats with JID for iOS platform""" result = get_chat_condition(["1234567890"], False, ["phone", "name"], "jid", "ios") assert result == "AND ( phone NOT LIKE '%1234567890%' AND (name NOT LIKE '%1234567890%' AND jid IS NOT NULL))" def test_multiple_chats_with_jid_android(self): """Test multiple chats with JID for Android platform""" result = get_chat_condition(["1234567890", "0987654321"], True, ["phone", "name"], "jid", "android") expected = "AND ( phone LIKE '%1234567890%' OR (name LIKE '%1234567890%' AND jid.type == 1) OR phone LIKE '%0987654321%' OR (name LIKE '%0987654321%' AND jid.type == 1))" assert result == expected def test_multiple_chats_exclude_with_jid_android(self): """Test excluding multiple chats with JID for Android platform""" result = get_chat_condition(["1234567890", "0987654321"], False, ["phone", "name"], "jid", "android") expected = "AND ( phone NOT LIKE '%1234567890%' AND (name NOT LIKE '%1234567890%' AND jid.type == 1) AND phone NOT LIKE '%0987654321%' AND (name NOT LIKE '%0987654321%' AND jid.type == 1))" assert result == expected def test_invalid_column_count_with_jid(self): """Test error when column count is less than 2 but jid is provided""" with pytest.raises(ValueError, match="There must be at least two elements in argument columns if jid is not None"): get_chat_condition(["1234567890"], True, ["phone"], "jid", "android") def test_unsupported_platform(self): """Test error when unsupported platform is provided""" with pytest.raises(ValueError, match="Only android and ios are supported for argument platform if jid is not None"): get_chat_condition(["1234567890"], True, ["phone", "name"], "jid", "windows") def test_empty_filter_list(self): """Test with empty filter list""" result = get_chat_condition([], True, ["phone"]) assert result == "" result = get_chat_condition([], False, ["phone"]) assert result == "" def test_filter_with_empty_strings(self): """Test with filter containing empty strings""" result = get_chat_condition(["", "1234567890"], True, ["phone"]) assert result == "AND ( phone LIKE '%%' OR phone LIKE '%1234567890%')" result = get_chat_condition([""], True, ["phone"]) assert result == "AND ( phone LIKE '%%')" def test_special_characters_in_filter(self): """Test with special characters in filter values""" result = get_chat_condition(["test@example.com"], True, ["email"]) assert result == "AND ( email LIKE '%test@example.com%')" result = get_chat_condition(["user-name"], True, ["username"]) assert result == "AND ( username LIKE '%user-name%')"