diff --git a/tests/unit/converter/test_add_image_text_converter.py b/tests/unit/prompt_converter/test_add_image_text_converter.py similarity index 100% rename from tests/unit/converter/test_add_image_text_converter.py rename to tests/unit/prompt_converter/test_add_image_text_converter.py diff --git a/tests/unit/converter/test_add_image_video_converter.py b/tests/unit/prompt_converter/test_add_image_video_converter.py similarity index 100% rename from tests/unit/converter/test_add_image_video_converter.py rename to tests/unit/prompt_converter/test_add_image_video_converter.py diff --git a/tests/unit/converter/test_add_text_image_converter.py b/tests/unit/prompt_converter/test_add_text_image_converter.py similarity index 100% rename from tests/unit/converter/test_add_text_image_converter.py rename to tests/unit/prompt_converter/test_add_text_image_converter.py diff --git a/tests/unit/converter/test_ansi_attack_converter.py b/tests/unit/prompt_converter/test_ansi_attack_converter.py similarity index 100% rename from tests/unit/converter/test_ansi_attack_converter.py rename to tests/unit/prompt_converter/test_ansi_attack_converter.py diff --git a/tests/unit/prompt_converter/test_ascii_art_converter.py b/tests/unit/prompt_converter/test_ascii_art_converter.py new file mode 100644 index 0000000000..a5cd716246 --- /dev/null +++ b/tests/unit/prompt_converter/test_ascii_art_converter.py @@ -0,0 +1,53 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. + +import pytest + +pytest.importorskip("art") + +from pyrit.prompt_converter import AsciiArtConverter, ConverterResult + + +@pytest.mark.asyncio +async def test_ascii_art_converter_basic(): + converter = AsciiArtConverter(font="block") + result = await converter.convert_async(prompt="hi", input_type="text") + assert isinstance(result, ConverterResult) + assert result.output_type == "text" + assert len(result.output_text) > 0 + assert "\n" in result.output_text + + +@pytest.mark.asyncio +async def test_ascii_art_converter_default_random_font(): + converter = AsciiArtConverter() + result = await converter.convert_async(prompt="test", input_type="text") + assert isinstance(result, ConverterResult) + assert len(result.output_text) > 0 + + +@pytest.mark.asyncio +async def test_ascii_art_converter_empty(): + converter = AsciiArtConverter(font="block") + result = await converter.convert_async(prompt="", input_type="text") + assert isinstance(result, ConverterResult) + assert result.output_type == "text" + + +@pytest.mark.asyncio +async def test_ascii_art_converter_input_not_supported(): + converter = AsciiArtConverter() + with pytest.raises(ValueError, match="Input type not supported"): + await converter.convert_async(prompt="test", input_type="image_path") + + +def test_ascii_art_converter_input_supported(): + converter = AsciiArtConverter() + assert converter.input_supported("text") is True + assert converter.input_supported("image_path") is False + + +def test_ascii_art_converter_output_supported(): + converter = AsciiArtConverter() + assert converter.output_supported("text") is True + assert converter.output_supported("image_path") is False diff --git a/tests/unit/prompt_converter/test_ascii_smuggler_converter.py b/tests/unit/prompt_converter/test_ascii_smuggler_converter.py new file mode 100644 index 0000000000..5b5caf0690 --- /dev/null +++ b/tests/unit/prompt_converter/test_ascii_smuggler_converter.py @@ -0,0 +1,53 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. + +import pytest + +from pyrit.prompt_converter import AsciiSmugglerConverter, ConverterResult + + +@pytest.mark.asyncio +async def test_ascii_smuggler_encode_basic(): + converter = AsciiSmugglerConverter(action="encode") + result = await converter.convert_async(prompt="hi", input_type="text") + assert isinstance(result, ConverterResult) + assert result.output_type == "text" + for char in result.output_text: + assert ord(char) > 0xE0000 + + +@pytest.mark.asyncio +async def test_ascii_smuggler_decode_roundtrip(): + encoder = AsciiSmugglerConverter(action="encode") + encoded = await encoder.convert_async(prompt="hello", input_type="text") + + decoder = AsciiSmugglerConverter(action="decode") + decoded = await decoder.convert_async(prompt=encoded.output_text, input_type="text") + assert decoded.output_text == "hello" + + +@pytest.mark.asyncio +async def test_ascii_smuggler_with_unicode_tags(): + converter = AsciiSmugglerConverter(action="encode", unicode_tags=True) + result = await converter.convert_async(prompt="hi", input_type="text") + assert result.output_text.startswith(chr(0xE0001)) + assert result.output_text.endswith(chr(0xE007F)) + + +@pytest.mark.asyncio +async def test_ascii_smuggler_empty(): + converter = AsciiSmugglerConverter(action="encode") + result = await converter.convert_async(prompt="", input_type="text") + assert result.output_text == "" + + +def test_ascii_smuggler_invalid_action(): + with pytest.raises(ValueError): + AsciiSmugglerConverter(action="invalid") + + +@pytest.mark.asyncio +async def test_ascii_smuggler_input_not_supported(): + converter = AsciiSmugglerConverter(action="encode") + with pytest.raises(ValueError, match="Input type not supported"): + await converter.convert_async(prompt="test", input_type="image_path") diff --git a/tests/unit/converter/test_ask_to_decode_converter.py b/tests/unit/prompt_converter/test_ask_to_decode_converter.py similarity index 100% rename from tests/unit/converter/test_ask_to_decode_converter.py rename to tests/unit/prompt_converter/test_ask_to_decode_converter.py diff --git a/tests/unit/prompt_converter/test_atbash_converter.py b/tests/unit/prompt_converter/test_atbash_converter.py new file mode 100644 index 0000000000..f8696d53c6 --- /dev/null +++ b/tests/unit/prompt_converter/test_atbash_converter.py @@ -0,0 +1,80 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. + +import pytest + +from pyrit.prompt_converter import AtbashConverter, ConverterResult + + +@pytest.mark.asyncio +async def test_atbash_converter_basic(): + converter = AtbashConverter() + result = await converter.convert_async(prompt="abc", input_type="text") + assert isinstance(result, ConverterResult) + assert result.output_text == "zyx" + assert result.output_type == "text" + + +@pytest.mark.asyncio +async def test_atbash_converter_uppercase(): + converter = AtbashConverter() + result = await converter.convert_async(prompt="ABC", input_type="text") + assert isinstance(result, ConverterResult) + assert result.output_text == "ZYX" + assert result.output_type == "text" + + +@pytest.mark.asyncio +async def test_atbash_converter_mixed(): + converter = AtbashConverter() + result = await converter.convert_async(prompt="Hello", input_type="text") + assert isinstance(result, ConverterResult) + assert result.output_text == "Svool" + assert result.output_type == "text" + + +@pytest.mark.asyncio +async def test_atbash_converter_with_description(): + converter = AtbashConverter(append_description=True) + result = await converter.convert_async(prompt="hello", input_type="text") + assert isinstance(result, ConverterResult) + assert result.output_type == "text" + # The encoded prompt should be present in the output + assert "svool" in result.output_text + + +@pytest.mark.asyncio +async def test_atbash_converter_empty_string(): + converter = AtbashConverter() + result = await converter.convert_async(prompt="", input_type="text") + assert isinstance(result, ConverterResult) + assert result.output_text == "" + assert result.output_type == "text" + + +@pytest.mark.asyncio +async def test_atbash_converter_numbers(): + converter = AtbashConverter() + result = await converter.convert_async(prompt="012", input_type="text") + assert isinstance(result, ConverterResult) + assert result.output_text == "987" + assert result.output_type == "text" + + +@pytest.mark.asyncio +async def test_atbash_converter_input_not_supported(): + converter = AtbashConverter() + with pytest.raises(ValueError): + await converter.convert_async(prompt="hello", input_type="image_path") + + +def test_atbash_converter_input_supported(): + converter = AtbashConverter() + assert converter.input_supported("text") is True + assert converter.input_supported("image_path") is False + + +def test_atbash_converter_output_supported(): + converter = AtbashConverter() + assert converter.output_supported("text") is True + assert converter.output_supported("image_path") is False diff --git a/tests/unit/converter/test_audio_echo_converter.py b/tests/unit/prompt_converter/test_audio_echo_converter.py similarity index 100% rename from tests/unit/converter/test_audio_echo_converter.py rename to tests/unit/prompt_converter/test_audio_echo_converter.py diff --git a/tests/unit/converter/test_audio_frequency_converter.py b/tests/unit/prompt_converter/test_audio_frequency_converter.py similarity index 100% rename from tests/unit/converter/test_audio_frequency_converter.py rename to tests/unit/prompt_converter/test_audio_frequency_converter.py diff --git a/tests/unit/converter/test_audio_speed_converter.py b/tests/unit/prompt_converter/test_audio_speed_converter.py similarity index 100% rename from tests/unit/converter/test_audio_speed_converter.py rename to tests/unit/prompt_converter/test_audio_speed_converter.py diff --git a/tests/unit/converter/test_audio_volume_converter.py b/tests/unit/prompt_converter/test_audio_volume_converter.py similarity index 100% rename from tests/unit/converter/test_audio_volume_converter.py rename to tests/unit/prompt_converter/test_audio_volume_converter.py diff --git a/tests/unit/converter/test_audio_white_noise_converter.py b/tests/unit/prompt_converter/test_audio_white_noise_converter.py similarity index 100% rename from tests/unit/converter/test_audio_white_noise_converter.py rename to tests/unit/prompt_converter/test_audio_white_noise_converter.py diff --git a/tests/unit/converter/test_azure_speech_converter.py b/tests/unit/prompt_converter/test_azure_speech_converter.py similarity index 100% rename from tests/unit/converter/test_azure_speech_converter.py rename to tests/unit/prompt_converter/test_azure_speech_converter.py diff --git a/tests/unit/converter/test_azure_speech_text_converter.py b/tests/unit/prompt_converter/test_azure_speech_text_converter.py similarity index 100% rename from tests/unit/converter/test_azure_speech_text_converter.py rename to tests/unit/prompt_converter/test_azure_speech_text_converter.py diff --git a/tests/unit/converter/test_base2048_converter.py b/tests/unit/prompt_converter/test_base2048_converter.py similarity index 100% rename from tests/unit/converter/test_base2048_converter.py rename to tests/unit/prompt_converter/test_base2048_converter.py diff --git a/tests/unit/prompt_converter/test_base64_converter.py b/tests/unit/prompt_converter/test_base64_converter.py new file mode 100644 index 0000000000..2eb87f1cb3 --- /dev/null +++ b/tests/unit/prompt_converter/test_base64_converter.py @@ -0,0 +1,61 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. + +import pytest + +from pyrit.prompt_converter import Base64Converter, ConverterResult + + +@pytest.mark.asyncio +async def test_base64_converter_default(): + converter = Base64Converter() + result = await converter.convert_async(prompt="hello", input_type="text") + assert isinstance(result, ConverterResult) + assert result.output_text == "aGVsbG8=" + assert result.output_type == "text" + + +@pytest.mark.asyncio +async def test_base64_converter_urlsafe(): + converter = Base64Converter(encoding_func="urlsafe_b64encode") + result = await converter.convert_async(prompt="hello", input_type="text") + assert isinstance(result, ConverterResult) + assert result.output_text == "aGVsbG8=" + assert result.output_type == "text" + + +@pytest.mark.asyncio +async def test_base64_converter_b16(): + converter = Base64Converter(encoding_func="b16encode") + result = await converter.convert_async(prompt="hi", input_type="text") + assert isinstance(result, ConverterResult) + assert result.output_text == "6869" + assert result.output_type == "text" + + +@pytest.mark.asyncio +async def test_base64_converter_empty(): + converter = Base64Converter() + result = await converter.convert_async(prompt="", input_type="text") + assert isinstance(result, ConverterResult) + assert result.output_text == "" + assert result.output_type == "text" + + +@pytest.mark.asyncio +async def test_base64_converter_input_not_supported(): + converter = Base64Converter() + with pytest.raises(ValueError): + await converter.convert_async(prompt="hello", input_type="image_path") + + +def test_base64_converter_input_supported(): + converter = Base64Converter() + assert converter.input_supported("text") is True + assert converter.input_supported("image_path") is False + + +def test_base64_converter_output_supported(): + converter = Base64Converter() + assert converter.output_supported("text") is True + assert converter.output_supported("image_path") is False diff --git a/tests/unit/converter/test_bin_ascii_converter.py b/tests/unit/prompt_converter/test_bin_ascii_converter.py similarity index 100% rename from tests/unit/converter/test_bin_ascii_converter.py rename to tests/unit/prompt_converter/test_bin_ascii_converter.py diff --git a/tests/unit/converter/test_binary_converter.py b/tests/unit/prompt_converter/test_binary_converter.py similarity index 100% rename from tests/unit/converter/test_binary_converter.py rename to tests/unit/prompt_converter/test_binary_converter.py diff --git a/tests/unit/converter/test_braille_converter.py b/tests/unit/prompt_converter/test_braille_converter.py similarity index 100% rename from tests/unit/converter/test_braille_converter.py rename to tests/unit/prompt_converter/test_braille_converter.py diff --git a/tests/unit/prompt_converter/test_caesar_converter.py b/tests/unit/prompt_converter/test_caesar_converter.py new file mode 100644 index 0000000000..51d8aff76f --- /dev/null +++ b/tests/unit/prompt_converter/test_caesar_converter.py @@ -0,0 +1,78 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. + +import pytest + +from pyrit.prompt_converter import CaesarConverter, ConverterResult + + +@pytest.mark.asyncio +async def test_caesar_converter_shift_1(): + converter = CaesarConverter(caesar_offset=1) + result = await converter.convert_async(prompt="abc", input_type="text") + assert isinstance(result, ConverterResult) + assert result.output_text == "bcd" + assert result.output_type == "text" + + +@pytest.mark.asyncio +async def test_caesar_converter_shift_negative(): + converter = CaesarConverter(caesar_offset=-1) + result = await converter.convert_async(prompt="bcd", input_type="text") + assert isinstance(result, ConverterResult) + assert result.output_text == "abc" + assert result.output_type == "text" + + +@pytest.mark.asyncio +async def test_caesar_converter_wraps_around(): + converter = CaesarConverter(caesar_offset=1) + result = await converter.convert_async(prompt="xyz", input_type="text") + assert isinstance(result, ConverterResult) + assert result.output_text == "yza" + assert result.output_type == "text" + + +@pytest.mark.asyncio +async def test_caesar_converter_preserves_case(): + converter = CaesarConverter(caesar_offset=1) + result = await converter.convert_async(prompt="AbC", input_type="text") + assert isinstance(result, ConverterResult) + assert result.output_text == "BcD" + assert result.output_type == "text" + + +@pytest.mark.asyncio +async def test_caesar_converter_with_description(): + converter = CaesarConverter(caesar_offset=1, append_description=True) + result = await converter.convert_async(prompt="hello", input_type="text") + assert isinstance(result, ConverterResult) + assert result.output_type == "text" + # The encoded prompt should be present in the output + assert "ifmmp" in result.output_text + + +def test_caesar_converter_invalid_offset(): + with pytest.raises(ValueError, match="caesar offset value invalid"): + CaesarConverter(caesar_offset=26) + + +def test_caesar_converter_invalid_negative_offset(): + with pytest.raises(ValueError, match="caesar offset value invalid"): + CaesarConverter(caesar_offset=-26) + + +@pytest.mark.asyncio +async def test_caesar_converter_empty(): + converter = CaesarConverter(caesar_offset=1) + result = await converter.convert_async(prompt="", input_type="text") + assert isinstance(result, ConverterResult) + assert result.output_text == "" + assert result.output_type == "text" + + +@pytest.mark.asyncio +async def test_caesar_converter_input_not_supported(): + converter = CaesarConverter(caesar_offset=1) + with pytest.raises(ValueError): + await converter.convert_async(prompt="hello", input_type="image_path") diff --git a/tests/unit/converter/test_char_swap_generator_converter.py b/tests/unit/prompt_converter/test_char_swap_generator_converter.py similarity index 100% rename from tests/unit/converter/test_char_swap_generator_converter.py rename to tests/unit/prompt_converter/test_char_swap_generator_converter.py diff --git a/tests/unit/prompt_converter/test_character_space_converter.py b/tests/unit/prompt_converter/test_character_space_converter.py new file mode 100644 index 0000000000..db9f8b38ec --- /dev/null +++ b/tests/unit/prompt_converter/test_character_space_converter.py @@ -0,0 +1,52 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. + +import pytest + +from pyrit.prompt_converter import CharacterSpaceConverter, ConverterResult + + +@pytest.mark.asyncio +async def test_character_space_basic(): + converter = CharacterSpaceConverter() + result = await converter.convert_async(prompt="hello", input_type="text") + assert isinstance(result, ConverterResult) + assert result.output_text == "h e l l o" + assert result.output_type == "text" + + +@pytest.mark.asyncio +async def test_character_space_removes_punctuation(): + converter = CharacterSpaceConverter() + result = await converter.convert_async(prompt="hello!", input_type="text") + assert isinstance(result, ConverterResult) + # "hello!" -> " ".join -> "h e l l o !" -> remove "!" -> "h e l l o " + assert "h e l l o" in result.output_text + assert "!" not in result.output_text + assert result.output_type == "text" + + +@pytest.mark.asyncio +async def test_character_space_with_spaces(): + converter = CharacterSpaceConverter() + result = await converter.convert_async(prompt="hi there", input_type="text") + assert isinstance(result, ConverterResult) + # "hi there" -> " ".join -> "h i t h e r e" (3 spaces between i and t) + assert result.output_text == "h i t h e r e" + assert result.output_type == "text" + + +@pytest.mark.asyncio +async def test_character_space_empty(): + converter = CharacterSpaceConverter() + result = await converter.convert_async(prompt="", input_type="text") + assert isinstance(result, ConverterResult) + assert result.output_text == "" + assert result.output_type == "text" + + +@pytest.mark.asyncio +async def test_character_space_input_not_supported(): + converter = CharacterSpaceConverter() + with pytest.raises(ValueError): + await converter.convert_async(prompt="hello", input_type="image_path") diff --git a/tests/unit/converter/test_code_chameleon_converter.py b/tests/unit/prompt_converter/test_code_chameleon_converter.py similarity index 100% rename from tests/unit/converter/test_code_chameleon_converter.py rename to tests/unit/prompt_converter/test_code_chameleon_converter.py diff --git a/tests/unit/converter/test_colloquial_wordswap_converter.py b/tests/unit/prompt_converter/test_colloquial_wordswap_converter.py similarity index 100% rename from tests/unit/converter/test_colloquial_wordswap_converter.py rename to tests/unit/prompt_converter/test_colloquial_wordswap_converter.py diff --git a/tests/unit/converter/test_denylist_converter.py b/tests/unit/prompt_converter/test_denylist_converter.py similarity index 100% rename from tests/unit/converter/test_denylist_converter.py rename to tests/unit/prompt_converter/test_denylist_converter.py diff --git a/tests/unit/converter/test_diacritics_converter.py b/tests/unit/prompt_converter/test_diacritics_converter.py similarity index 100% rename from tests/unit/converter/test_diacritics_converter.py rename to tests/unit/prompt_converter/test_diacritics_converter.py diff --git a/tests/unit/converter/test_ecoji_converter.py b/tests/unit/prompt_converter/test_ecoji_converter.py similarity index 100% rename from tests/unit/converter/test_ecoji_converter.py rename to tests/unit/prompt_converter/test_ecoji_converter.py diff --git a/tests/unit/prompt_converter/test_emoji_converter.py b/tests/unit/prompt_converter/test_emoji_converter.py new file mode 100644 index 0000000000..8897febe90 --- /dev/null +++ b/tests/unit/prompt_converter/test_emoji_converter.py @@ -0,0 +1,50 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. + +import pytest + +from pyrit.prompt_converter import ConverterResult, EmojiConverter + + +@pytest.mark.asyncio +async def test_emoji_converter_basic(): + converter = EmojiConverter() + result = await converter.convert_async(prompt="a", input_type="text") + assert isinstance(result, ConverterResult) + assert result.output_text in EmojiConverter.emoji_dict["a"] + assert result.output_type == "text" + + +@pytest.mark.asyncio +async def test_emoji_converter_produces_output(): + converter = EmojiConverter() + result = await converter.convert_async(prompt="hello", input_type="text") + assert isinstance(result, ConverterResult) + assert len(result.output_text) > 0 + assert result.output_text != "hello" + assert result.output_type == "text" + + +@pytest.mark.asyncio +async def test_emoji_converter_empty(): + converter = EmojiConverter() + result = await converter.convert_async(prompt="", input_type="text") + assert isinstance(result, ConverterResult) + assert result.output_text == "" + assert result.output_type == "text" + + +@pytest.mark.asyncio +async def test_emoji_converter_numbers_unchanged(): + converter = EmojiConverter() + result = await converter.convert_async(prompt="123", input_type="text") + assert isinstance(result, ConverterResult) + assert result.output_text == "123" + assert result.output_type == "text" + + +@pytest.mark.asyncio +async def test_emoji_converter_input_not_supported(): + converter = EmojiConverter() + with pytest.raises(ValueError): + await converter.convert_async(prompt="hello", input_type="image_path") diff --git a/tests/unit/converter/test_first_letter_converter.py b/tests/unit/prompt_converter/test_first_letter_converter.py similarity index 100% rename from tests/unit/converter/test_first_letter_converter.py rename to tests/unit/prompt_converter/test_first_letter_converter.py diff --git a/tests/unit/prompt_converter/test_flip_converter.py b/tests/unit/prompt_converter/test_flip_converter.py new file mode 100644 index 0000000000..8dc79c7e21 --- /dev/null +++ b/tests/unit/prompt_converter/test_flip_converter.py @@ -0,0 +1,58 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. + +import pytest + +from pyrit.prompt_converter import ConverterResult, FlipConverter + + +@pytest.mark.asyncio +async def test_flip_converter_basic(): + converter = FlipConverter() + result = await converter.convert_async(prompt="hello", input_type="text") + assert isinstance(result, ConverterResult) + assert result.output_text == "olleh" + assert result.output_type == "text" + + +@pytest.mark.asyncio +async def test_flip_converter_sentence(): + converter = FlipConverter() + result = await converter.convert_async(prompt="hello world", input_type="text") + assert isinstance(result, ConverterResult) + assert result.output_text == "dlrow olleh" + assert result.output_type == "text" + + +@pytest.mark.asyncio +async def test_flip_converter_empty(): + converter = FlipConverter() + result = await converter.convert_async(prompt="", input_type="text") + assert isinstance(result, ConverterResult) + assert result.output_text == "" + assert result.output_type == "text" + + +@pytest.mark.asyncio +async def test_flip_converter_single_char(): + converter = FlipConverter() + result = await converter.convert_async(prompt="a", input_type="text") + assert isinstance(result, ConverterResult) + assert result.output_text == "a" + assert result.output_type == "text" + + +@pytest.mark.asyncio +async def test_flip_converter_palindrome(): + converter = FlipConverter() + result = await converter.convert_async(prompt="racecar", input_type="text") + assert isinstance(result, ConverterResult) + assert result.output_text == "racecar" + assert result.output_type == "text" + + +@pytest.mark.asyncio +async def test_flip_converter_input_not_supported(): + converter = FlipConverter() + with pytest.raises(ValueError): + await converter.convert_async(prompt="hello", input_type="image_path") diff --git a/tests/unit/converter/test_generic_llm_converter.py b/tests/unit/prompt_converter/test_generic_llm_converter.py similarity index 100% rename from tests/unit/converter/test_generic_llm_converter.py rename to tests/unit/prompt_converter/test_generic_llm_converter.py diff --git a/tests/unit/converter/test_image_compression_converter.py b/tests/unit/prompt_converter/test_image_compression_converter.py similarity index 100% rename from tests/unit/converter/test_image_compression_converter.py rename to tests/unit/prompt_converter/test_image_compression_converter.py diff --git a/tests/unit/converter/test_insert_punctuation_converter.py b/tests/unit/prompt_converter/test_insert_punctuation_converter.py similarity index 100% rename from tests/unit/converter/test_insert_punctuation_converter.py rename to tests/unit/prompt_converter/test_insert_punctuation_converter.py diff --git a/tests/unit/converter/test_json_string_converter.py b/tests/unit/prompt_converter/test_json_string_converter.py similarity index 100% rename from tests/unit/converter/test_json_string_converter.py rename to tests/unit/prompt_converter/test_json_string_converter.py diff --git a/tests/unit/converter/test_leetspeak_converter.py b/tests/unit/prompt_converter/test_leetspeak_converter.py similarity index 100% rename from tests/unit/converter/test_leetspeak_converter.py rename to tests/unit/prompt_converter/test_leetspeak_converter.py diff --git a/tests/unit/converter/test_math_obfuscation_converter.py b/tests/unit/prompt_converter/test_math_obfuscation_converter.py similarity index 100% rename from tests/unit/converter/test_math_obfuscation_converter.py rename to tests/unit/prompt_converter/test_math_obfuscation_converter.py diff --git a/tests/unit/converter/test_math_prompt_converter.py b/tests/unit/prompt_converter/test_math_prompt_converter.py similarity index 100% rename from tests/unit/converter/test_math_prompt_converter.py rename to tests/unit/prompt_converter/test_math_prompt_converter.py diff --git a/tests/unit/prompt_converter/test_morse_converter.py b/tests/unit/prompt_converter/test_morse_converter.py new file mode 100644 index 0000000000..3b855863ab --- /dev/null +++ b/tests/unit/prompt_converter/test_morse_converter.py @@ -0,0 +1,59 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. + +import pytest + +from pyrit.prompt_converter import ConverterResult, MorseConverter + + +@pytest.mark.asyncio +async def test_morse_converter_basic(): + converter = MorseConverter() + result = await converter.convert_async(prompt="hi", input_type="text") + assert isinstance(result, ConverterResult) + assert result.output_text == ".... .." + assert result.output_type == "text" + + +@pytest.mark.asyncio +async def test_morse_converter_word_separator(): + converter = MorseConverter() + result = await converter.convert_async(prompt="hi hi", input_type="text") + assert isinstance(result, ConverterResult) + assert result.output_text == ".... .. / .... .." + assert result.output_type == "text" + + +@pytest.mark.asyncio +async def test_morse_converter_uppercase(): + converter = MorseConverter() + result = await converter.convert_async(prompt="HI", input_type="text") + assert isinstance(result, ConverterResult) + assert result.output_text == ".... .." + assert result.output_type == "text" + + +@pytest.mark.asyncio +async def test_morse_converter_with_description(): + converter = MorseConverter(append_description=True) + result = await converter.convert_async(prompt="hi", input_type="text") + assert isinstance(result, ConverterResult) + assert result.output_type == "text" + # The encoded prompt should be present in the output + assert ".... .." in result.output_text + + +@pytest.mark.asyncio +async def test_morse_converter_empty(): + converter = MorseConverter() + result = await converter.convert_async(prompt="", input_type="text") + assert isinstance(result, ConverterResult) + assert result.output_text == "" + assert result.output_type == "text" + + +@pytest.mark.asyncio +async def test_morse_converter_input_not_supported(): + converter = MorseConverter() + with pytest.raises(ValueError): + await converter.convert_async(prompt="hello", input_type="image_path") diff --git a/tests/unit/converter/test_nato_converter.py b/tests/unit/prompt_converter/test_nato_converter.py similarity index 100% rename from tests/unit/converter/test_nato_converter.py rename to tests/unit/prompt_converter/test_nato_converter.py diff --git a/tests/unit/converter/test_negation_trap_converter.py b/tests/unit/prompt_converter/test_negation_trap_converter.py similarity index 100% rename from tests/unit/converter/test_negation_trap_converter.py rename to tests/unit/prompt_converter/test_negation_trap_converter.py diff --git a/tests/unit/converter/test_pdf_converter.py b/tests/unit/prompt_converter/test_pdf_converter.py similarity index 100% rename from tests/unit/converter/test_pdf_converter.py rename to tests/unit/prompt_converter/test_pdf_converter.py diff --git a/tests/unit/converter/test_persuasion_converter.py b/tests/unit/prompt_converter/test_persuasion_converter.py similarity index 100% rename from tests/unit/converter/test_persuasion_converter.py rename to tests/unit/prompt_converter/test_persuasion_converter.py diff --git a/tests/unit/converter/test_prompt_converter.py b/tests/unit/prompt_converter/test_prompt_converter.py similarity index 100% rename from tests/unit/converter/test_prompt_converter.py rename to tests/unit/prompt_converter/test_prompt_converter.py diff --git a/tests/unit/converter/test_qr_code_converter.py b/tests/unit/prompt_converter/test_qr_code_converter.py similarity index 100% rename from tests/unit/converter/test_qr_code_converter.py rename to tests/unit/prompt_converter/test_qr_code_converter.py diff --git a/tests/unit/prompt_converter/test_random_capital_letters_converter.py b/tests/unit/prompt_converter/test_random_capital_letters_converter.py new file mode 100644 index 0000000000..2f87cbf0ce --- /dev/null +++ b/tests/unit/prompt_converter/test_random_capital_letters_converter.py @@ -0,0 +1,58 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. + +import pytest + +from pyrit.prompt_converter import ConverterResult, RandomCapitalLettersConverter + + +@pytest.mark.asyncio +async def test_random_capital_100_percent(): + converter = RandomCapitalLettersConverter(percentage=100.0) + result = await converter.convert_async(prompt="hello", input_type="text") + assert isinstance(result, ConverterResult) + assert result.output_text == "HELLO" + assert result.output_type == "text" + + +@pytest.mark.asyncio +async def test_random_capital_preserves_non_alpha(): + converter = RandomCapitalLettersConverter(percentage=100.0) + result = await converter.convert_async(prompt="hello123", input_type="text") + assert isinstance(result, ConverterResult) + assert result.output_text == "HELLO123" + assert result.output_type == "text" + + +@pytest.mark.asyncio +async def test_random_capital_0_not_allowed(): + converter = RandomCapitalLettersConverter(percentage=0) + with pytest.raises(ValueError): + await converter.convert_async(prompt="hello", input_type="text") + + +@pytest.mark.asyncio +async def test_random_capital_partial(): + converter = RandomCapitalLettersConverter(percentage=50.0) + result = await converter.convert_async(prompt="abcdefghij", input_type="text") + assert isinstance(result, ConverterResult) + assert len(result.output_text) == 10 + upper_count = sum(1 for c in result.output_text if c.isupper()) + assert upper_count > 0 + assert result.output_type == "text" + + +@pytest.mark.asyncio +async def test_random_capital_empty(): + converter = RandomCapitalLettersConverter(percentage=100.0) + result = await converter.convert_async(prompt="", input_type="text") + assert isinstance(result, ConverterResult) + assert result.output_text == "" + assert result.output_type == "text" + + +@pytest.mark.asyncio +async def test_random_capital_input_not_supported(): + converter = RandomCapitalLettersConverter() + with pytest.raises(ValueError): + await converter.convert_async(prompt="hello", input_type="image_path") diff --git a/tests/unit/converter/test_random_translation_converter.py b/tests/unit/prompt_converter/test_random_translation_converter.py similarity index 100% rename from tests/unit/converter/test_random_translation_converter.py rename to tests/unit/prompt_converter/test_random_translation_converter.py diff --git a/tests/unit/converter/test_repeat_token_converter.py b/tests/unit/prompt_converter/test_repeat_token_converter.py similarity index 100% rename from tests/unit/converter/test_repeat_token_converter.py rename to tests/unit/prompt_converter/test_repeat_token_converter.py diff --git a/tests/unit/prompt_converter/test_rot13_converter.py b/tests/unit/prompt_converter/test_rot13_converter.py new file mode 100644 index 0000000000..80987162be --- /dev/null +++ b/tests/unit/prompt_converter/test_rot13_converter.py @@ -0,0 +1,57 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. + +import pytest + +from pyrit.prompt_converter import ConverterResult, ROT13Converter + + +@pytest.mark.asyncio +async def test_rot13_converter_basic(): + converter = ROT13Converter() + result = await converter.convert_async(prompt="hello", input_type="text") + assert isinstance(result, ConverterResult) + assert result.output_text == "uryyb" + assert result.output_type == "text" + + +@pytest.mark.asyncio +async def test_rot13_converter_roundtrip(): + converter = ROT13Converter() + first = await converter.convert_async(prompt="hello", input_type="text") + second = await converter.convert_async(prompt=first.output_text, input_type="text") + assert second.output_text == "hello" + + +@pytest.mark.asyncio +async def test_rot13_converter_uppercase(): + converter = ROT13Converter() + result = await converter.convert_async(prompt="ABC", input_type="text") + assert isinstance(result, ConverterResult) + assert result.output_text == "NOP" + assert result.output_type == "text" + + +@pytest.mark.asyncio +async def test_rot13_converter_with_numbers(): + converter = ROT13Converter() + result = await converter.convert_async(prompt="hello123", input_type="text") + assert isinstance(result, ConverterResult) + assert result.output_text == "uryyb123" + assert result.output_type == "text" + + +@pytest.mark.asyncio +async def test_rot13_converter_empty(): + converter = ROT13Converter() + result = await converter.convert_async(prompt="", input_type="text") + assert isinstance(result, ConverterResult) + assert result.output_text == "" + assert result.output_type == "text" + + +@pytest.mark.asyncio +async def test_rot13_converter_input_not_supported(): + converter = ROT13Converter() + with pytest.raises(ValueError): + await converter.convert_async(prompt="hello", input_type="image_path") diff --git a/tests/unit/converter/test_scientific_translation_converter.py b/tests/unit/prompt_converter/test_scientific_translation_converter.py similarity index 100% rename from tests/unit/converter/test_scientific_translation_converter.py rename to tests/unit/prompt_converter/test_scientific_translation_converter.py diff --git a/tests/unit/prompt_converter/test_search_replace_converter.py b/tests/unit/prompt_converter/test_search_replace_converter.py new file mode 100644 index 0000000000..bf72520758 --- /dev/null +++ b/tests/unit/prompt_converter/test_search_replace_converter.py @@ -0,0 +1,58 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. + +import pytest + +from pyrit.prompt_converter import ConverterResult, SearchReplaceConverter + + +@pytest.mark.asyncio +async def test_search_replace_basic(): + converter = SearchReplaceConverter(pattern="hello", replace="world") + result = await converter.convert_async(prompt="hello there", input_type="text") + assert isinstance(result, ConverterResult) + assert result.output_text == "world there" + assert result.output_type == "text" + + +@pytest.mark.asyncio +async def test_search_replace_regex(): + converter = SearchReplaceConverter(pattern=r"\d+", replace="NUM") + result = await converter.convert_async(prompt="abc123def456", input_type="text") + assert isinstance(result, ConverterResult) + assert result.output_text == "abcNUMdefNUM" + assert result.output_type == "text" + + +@pytest.mark.asyncio +async def test_search_replace_list_replacement(): + converter = SearchReplaceConverter(pattern="hello", replace=["X"]) + result = await converter.convert_async(prompt="hello there", input_type="text") + assert isinstance(result, ConverterResult) + assert result.output_text == "X there" + assert result.output_type == "text" + + +@pytest.mark.asyncio +async def test_search_replace_no_match(): + converter = SearchReplaceConverter(pattern="xyz", replace="abc") + result = await converter.convert_async(prompt="hello", input_type="text") + assert isinstance(result, ConverterResult) + assert result.output_text == "hello" + assert result.output_type == "text" + + +@pytest.mark.asyncio +async def test_search_replace_empty_string(): + converter = SearchReplaceConverter(pattern="hello", replace="world") + result = await converter.convert_async(prompt="", input_type="text") + assert isinstance(result, ConverterResult) + assert result.output_text == "" + assert result.output_type == "text" + + +@pytest.mark.asyncio +async def test_search_replace_input_not_supported(): + converter = SearchReplaceConverter(pattern="hello", replace="world") + with pytest.raises(ValueError): + await converter.convert_async(prompt="hello", input_type="image_path") diff --git a/tests/unit/converter/test_selective_text_converter.py b/tests/unit/prompt_converter/test_selective_text_converter.py similarity index 100% rename from tests/unit/converter/test_selective_text_converter.py rename to tests/unit/prompt_converter/test_selective_text_converter.py diff --git a/tests/unit/prompt_converter/test_sneaky_bits_smuggler_converter.py b/tests/unit/prompt_converter/test_sneaky_bits_smuggler_converter.py new file mode 100644 index 0000000000..202921c37f --- /dev/null +++ b/tests/unit/prompt_converter/test_sneaky_bits_smuggler_converter.py @@ -0,0 +1,53 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. + +import pytest + +from pyrit.prompt_converter import ConverterResult, SneakyBitsSmugglerConverter + + +@pytest.mark.asyncio +async def test_sneaky_bits_encode_produces_invisible(): + converter = SneakyBitsSmugglerConverter(action="encode") + result = await converter.convert_async(prompt="hi", input_type="text") + assert isinstance(result, ConverterResult) + assert result.output_type == "text" + valid_chars = {converter.zero_char, converter.one_char} + assert all(ch in valid_chars for ch in result.output_text) + + +@pytest.mark.asyncio +async def test_sneaky_bits_decode_roundtrip(): + encoder = SneakyBitsSmugglerConverter(action="encode") + encoded = await encoder.convert_async(prompt="test message", input_type="text") + + decoder = SneakyBitsSmugglerConverter(action="decode") + decoded = await decoder.convert_async(prompt=encoded.output_text, input_type="text") + assert decoded.output_text == "test message" + + +@pytest.mark.asyncio +async def test_sneaky_bits_custom_chars(): + converter = SneakyBitsSmugglerConverter(action="encode", zero_char="0", one_char="1") + result = await converter.convert_async(prompt="A", input_type="text") + assert all(ch in {"0", "1"} for ch in result.output_text) + assert len(result.output_text) == 8 # 1 ASCII byte = 8 bits + + +@pytest.mark.asyncio +async def test_sneaky_bits_empty(): + converter = SneakyBitsSmugglerConverter(action="encode") + result = await converter.convert_async(prompt="", input_type="text") + assert result.output_text == "" + + +def test_sneaky_bits_invalid_action(): + with pytest.raises(ValueError): + SneakyBitsSmugglerConverter(action="invalid") + + +@pytest.mark.asyncio +async def test_sneaky_bits_input_not_supported(): + converter = SneakyBitsSmugglerConverter(action="encode") + with pytest.raises(ValueError, match="Input type not supported"): + await converter.convert_async(prompt="test", input_type="image_path") diff --git a/tests/unit/prompt_converter/test_string_join_converter.py b/tests/unit/prompt_converter/test_string_join_converter.py new file mode 100644 index 0000000000..c7cc0abb30 --- /dev/null +++ b/tests/unit/prompt_converter/test_string_join_converter.py @@ -0,0 +1,58 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. + +import pytest + +from pyrit.prompt_converter import ConverterResult, StringJoinConverter + + +@pytest.mark.asyncio +async def test_string_join_default(): + converter = StringJoinConverter() + result = await converter.convert_async(prompt="hello", input_type="text") + assert isinstance(result, ConverterResult) + assert result.output_text == "h-e-l-l-o" + assert result.output_type == "text" + + +@pytest.mark.asyncio +async def test_string_join_custom_separator(): + converter = StringJoinConverter(join_value=".") + result = await converter.convert_async(prompt="hi", input_type="text") + assert isinstance(result, ConverterResult) + assert result.output_text == "h.i" + assert result.output_type == "text" + + +@pytest.mark.asyncio +async def test_string_join_multi_word(): + converter = StringJoinConverter() + result = await converter.convert_async(prompt="hi there", input_type="text") + assert isinstance(result, ConverterResult) + assert result.output_text == "h-i t-h-e-r-e" + assert result.output_type == "text" + + +@pytest.mark.asyncio +async def test_string_join_empty(): + converter = StringJoinConverter() + result = await converter.convert_async(prompt="", input_type="text") + assert isinstance(result, ConverterResult) + assert result.output_text == "" + assert result.output_type == "text" + + +@pytest.mark.asyncio +async def test_string_join_single_char(): + converter = StringJoinConverter() + result = await converter.convert_async(prompt="a", input_type="text") + assert isinstance(result, ConverterResult) + assert result.output_text == "a" + assert result.output_type == "text" + + +@pytest.mark.asyncio +async def test_string_join_input_not_supported(): + converter = StringJoinConverter() + with pytest.raises(ValueError): + await converter.convert_async(prompt="hello", input_type="image_path") diff --git a/tests/unit/prompt_converter/test_suffix_append_converter.py b/tests/unit/prompt_converter/test_suffix_append_converter.py new file mode 100644 index 0000000000..a0839eb570 --- /dev/null +++ b/tests/unit/prompt_converter/test_suffix_append_converter.py @@ -0,0 +1,45 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. + +import pytest + +from pyrit.prompt_converter import ConverterResult, SuffixAppendConverter + + +@pytest.mark.asyncio +async def test_suffix_append_basic(): + converter = SuffixAppendConverter(suffix="!!!") + result = await converter.convert_async(prompt="hello", input_type="text") + assert isinstance(result, ConverterResult) + assert result.output_text == "hello !!!" + assert result.output_type == "text" + + +@pytest.mark.asyncio +async def test_suffix_append_long_suffix(): + converter = SuffixAppendConverter(suffix="please respond") + result = await converter.convert_async(prompt="test", input_type="text") + assert isinstance(result, ConverterResult) + assert result.output_text == "test please respond" + assert result.output_type == "text" + + +@pytest.mark.asyncio +async def test_suffix_append_empty_prompt(): + converter = SuffixAppendConverter(suffix="end") + result = await converter.convert_async(prompt="", input_type="text") + assert isinstance(result, ConverterResult) + assert result.output_text == " end" + assert result.output_type == "text" + + +def test_suffix_append_empty_suffix_raises(): + with pytest.raises(ValueError): + SuffixAppendConverter(suffix="") + + +@pytest.mark.asyncio +async def test_suffix_append_input_not_supported(): + converter = SuffixAppendConverter(suffix="end") + with pytest.raises(ValueError): + await converter.convert_async(prompt="hello", input_type="image_path") diff --git a/tests/unit/converter/test_superscript_converter.py b/tests/unit/prompt_converter/test_superscript_converter.py similarity index 100% rename from tests/unit/converter/test_superscript_converter.py rename to tests/unit/prompt_converter/test_superscript_converter.py diff --git a/tests/unit/converter/test_template_segment_converter.py b/tests/unit/prompt_converter/test_template_segment_converter.py similarity index 100% rename from tests/unit/converter/test_template_segment_converter.py rename to tests/unit/prompt_converter/test_template_segment_converter.py diff --git a/tests/unit/converter/test_text_jailbreak_converter.py b/tests/unit/prompt_converter/test_text_jailbreak_converter.py similarity index 100% rename from tests/unit/converter/test_text_jailbreak_converter.py rename to tests/unit/prompt_converter/test_text_jailbreak_converter.py diff --git a/tests/unit/converter/test_text_selection_strategy.py b/tests/unit/prompt_converter/test_text_selection_strategy.py similarity index 100% rename from tests/unit/converter/test_text_selection_strategy.py rename to tests/unit/prompt_converter/test_text_selection_strategy.py diff --git a/tests/unit/converter/test_token_smuggler_converter.py b/tests/unit/prompt_converter/test_token_smuggler_converter.py similarity index 100% rename from tests/unit/converter/test_token_smuggler_converter.py rename to tests/unit/prompt_converter/test_token_smuggler_converter.py diff --git a/tests/unit/converter/test_toxic_sentence_generator_converter.py b/tests/unit/prompt_converter/test_toxic_sentence_generator_converter.py similarity index 100% rename from tests/unit/converter/test_toxic_sentence_generator_converter.py rename to tests/unit/prompt_converter/test_toxic_sentence_generator_converter.py diff --git a/tests/unit/converter/test_translation_converter.py b/tests/unit/prompt_converter/test_translation_converter.py similarity index 100% rename from tests/unit/converter/test_translation_converter.py rename to tests/unit/prompt_converter/test_translation_converter.py diff --git a/tests/unit/converter/test_transparency_attack_converter.py b/tests/unit/prompt_converter/test_transparency_attack_converter.py similarity index 100% rename from tests/unit/converter/test_transparency_attack_converter.py rename to tests/unit/prompt_converter/test_transparency_attack_converter.py diff --git a/tests/unit/converter/test_unicode_confusable_converter.py b/tests/unit/prompt_converter/test_unicode_confusable_converter.py similarity index 100% rename from tests/unit/converter/test_unicode_confusable_converter.py rename to tests/unit/prompt_converter/test_unicode_confusable_converter.py diff --git a/tests/unit/prompt_converter/test_unicode_replacement_converter.py b/tests/unit/prompt_converter/test_unicode_replacement_converter.py new file mode 100644 index 0000000000..0650fceaf3 --- /dev/null +++ b/tests/unit/prompt_converter/test_unicode_replacement_converter.py @@ -0,0 +1,58 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. + +import pytest + +from pyrit.prompt_converter import ConverterResult, UnicodeReplacementConverter + + +@pytest.mark.asyncio +async def test_unicode_replacement_basic(): + converter = UnicodeReplacementConverter() + result = await converter.convert_async(prompt="a", input_type="text") + assert isinstance(result, ConverterResult) + assert result.output_text == "\\u0061" + assert result.output_type == "text" + + +@pytest.mark.asyncio +async def test_unicode_replacement_word(): + converter = UnicodeReplacementConverter() + result = await converter.convert_async(prompt="hi", input_type="text") + assert isinstance(result, ConverterResult) + assert result.output_text == "\\u0068\\u0069" + assert result.output_type == "text" + + +@pytest.mark.asyncio +async def test_unicode_replacement_with_spaces(): + converter = UnicodeReplacementConverter(encode_spaces=False) + result = await converter.convert_async(prompt="a b", input_type="text") + assert isinstance(result, ConverterResult) + assert result.output_text == "\\u0061 \\u0062" + assert result.output_type == "text" + + +@pytest.mark.asyncio +async def test_unicode_replacement_encode_spaces(): + converter = UnicodeReplacementConverter(encode_spaces=True) + result = await converter.convert_async(prompt="a b", input_type="text") + assert isinstance(result, ConverterResult) + assert result.output_text == "\\u0061\\u0020\\u0062" + assert result.output_type == "text" + + +@pytest.mark.asyncio +async def test_unicode_replacement_empty(): + converter = UnicodeReplacementConverter() + result = await converter.convert_async(prompt="", input_type="text") + assert isinstance(result, ConverterResult) + assert result.output_text == "" + assert result.output_type == "text" + + +@pytest.mark.asyncio +async def test_unicode_replacement_input_not_supported(): + converter = UnicodeReplacementConverter() + with pytest.raises(ValueError): + await converter.convert_async(prompt="hello", input_type="image_path") diff --git a/tests/unit/prompt_converter/test_unicode_sub_converter.py b/tests/unit/prompt_converter/test_unicode_sub_converter.py new file mode 100644 index 0000000000..c1eead7276 --- /dev/null +++ b/tests/unit/prompt_converter/test_unicode_sub_converter.py @@ -0,0 +1,50 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. + +import pytest + +from pyrit.prompt_converter import ConverterResult, UnicodeSubstitutionConverter + + +@pytest.mark.asyncio +async def test_unicode_sub_basic(): + converter = UnicodeSubstitutionConverter() + result = await converter.convert_async(prompt="a", input_type="text") + assert isinstance(result, ConverterResult) + assert result.output_text == chr(0xE0000 + ord("a")) + assert result.output_type == "text" + + +@pytest.mark.asyncio +async def test_unicode_sub_custom_start(): + converter = UnicodeSubstitutionConverter(start_value=0x1F600) + result = await converter.convert_async(prompt="a", input_type="text") + assert isinstance(result, ConverterResult) + assert result.output_text == chr(0x1F600 + ord("a")) + assert result.output_type == "text" + + +@pytest.mark.asyncio +async def test_unicode_sub_empty(): + converter = UnicodeSubstitutionConverter() + result = await converter.convert_async(prompt="", input_type="text") + assert isinstance(result, ConverterResult) + assert result.output_text == "" + assert result.output_type == "text" + + +@pytest.mark.asyncio +async def test_unicode_sub_multiple_chars(): + converter = UnicodeSubstitutionConverter() + result = await converter.convert_async(prompt="ab", input_type="text") + assert isinstance(result, ConverterResult) + expected = chr(0xE0000 + ord("a")) + chr(0xE0000 + ord("b")) + assert result.output_text == expected + assert result.output_type == "text" + + +@pytest.mark.asyncio +async def test_unicode_sub_input_not_supported(): + converter = UnicodeSubstitutionConverter() + with pytest.raises(ValueError): + await converter.convert_async(prompt="hello", input_type="image_path") diff --git a/tests/unit/prompt_converter/test_url_converter.py b/tests/unit/prompt_converter/test_url_converter.py new file mode 100644 index 0000000000..1586dadcda --- /dev/null +++ b/tests/unit/prompt_converter/test_url_converter.py @@ -0,0 +1,49 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. + +import pytest + +from pyrit.prompt_converter import ConverterResult, UrlConverter + + +@pytest.mark.asyncio +async def test_url_converter_basic(): + converter = UrlConverter() + result = await converter.convert_async(prompt="hello world", input_type="text") + assert isinstance(result, ConverterResult) + assert result.output_text == "hello%20world" + assert result.output_type == "text" + + +@pytest.mark.asyncio +async def test_url_converter_special_chars(): + converter = UrlConverter() + result = await converter.convert_async(prompt="a&b=c", input_type="text") + assert isinstance(result, ConverterResult) + assert result.output_text == "a%26b%3Dc" + assert result.output_type == "text" + + +@pytest.mark.asyncio +async def test_url_converter_already_safe(): + converter = UrlConverter() + result = await converter.convert_async(prompt="hello", input_type="text") + assert isinstance(result, ConverterResult) + assert result.output_text == "hello" + assert result.output_type == "text" + + +@pytest.mark.asyncio +async def test_url_converter_empty(): + converter = UrlConverter() + result = await converter.convert_async(prompt="", input_type="text") + assert isinstance(result, ConverterResult) + assert result.output_text == "" + assert result.output_type == "text" + + +@pytest.mark.asyncio +async def test_url_converter_input_not_supported(): + converter = UrlConverter() + with pytest.raises(ValueError): + await converter.convert_async(prompt="hello", input_type="image_path") diff --git a/tests/unit/converter/test_variation_converter.py b/tests/unit/prompt_converter/test_variation_converter.py similarity index 100% rename from tests/unit/converter/test_variation_converter.py rename to tests/unit/prompt_converter/test_variation_converter.py diff --git a/tests/unit/prompt_converter/test_variation_selector_smuggler_converter.py b/tests/unit/prompt_converter/test_variation_selector_smuggler_converter.py new file mode 100644 index 0000000000..8522bcb6f5 --- /dev/null +++ b/tests/unit/prompt_converter/test_variation_selector_smuggler_converter.py @@ -0,0 +1,54 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. + +import pytest + +from pyrit.prompt_converter import ConverterResult, VariationSelectorSmugglerConverter + + +@pytest.mark.asyncio +async def test_variation_selector_encode_basic(): + converter = VariationSelectorSmugglerConverter(action="encode") + result = await converter.convert_async(prompt="hi", input_type="text") + assert isinstance(result, ConverterResult) + assert result.output_type == "text" + assert len(result.output_text) > 0 + + +@pytest.mark.asyncio +async def test_variation_selector_decode_roundtrip(): + encoder = VariationSelectorSmugglerConverter(action="encode") + encoded = await encoder.convert_async(prompt="test", input_type="text") + + decoder = VariationSelectorSmugglerConverter(action="decode") + decoded = await decoder.convert_async(prompt=encoded.output_text, input_type="text") + assert decoded.output_text == "test" + + +@pytest.mark.asyncio +async def test_variation_selector_no_embed(): + converter = VariationSelectorSmugglerConverter(action="encode", embed_in_base=False) + result = await converter.convert_async(prompt="a", input_type="text") + base_char = converter.utf8_base_char + # With embed_in_base=False, a space separator is inserted after the base char + assert result.output_text.startswith(base_char + " ") + + +@pytest.mark.asyncio +async def test_variation_selector_empty(): + converter = VariationSelectorSmugglerConverter(action="encode") + result = await converter.convert_async(prompt="", input_type="text") + # Empty input still produces base char prefix + assert result.output_text == converter.utf8_base_char + + +def test_variation_selector_invalid_action(): + with pytest.raises(ValueError): + VariationSelectorSmugglerConverter(action="invalid") + + +@pytest.mark.asyncio +async def test_variation_selector_input_not_supported(): + converter = VariationSelectorSmugglerConverter(action="encode") + with pytest.raises(ValueError, match="Input type not supported"): + await converter.convert_async(prompt="test", input_type="image_path") diff --git a/tests/unit/converter/test_word_doc_converter.py b/tests/unit/prompt_converter/test_word_doc_converter.py similarity index 100% rename from tests/unit/converter/test_word_doc_converter.py rename to tests/unit/prompt_converter/test_word_doc_converter.py diff --git a/tests/unit/converter/test_word_level_converter.py b/tests/unit/prompt_converter/test_word_level_converter.py similarity index 100% rename from tests/unit/converter/test_word_level_converter.py rename to tests/unit/prompt_converter/test_word_level_converter.py diff --git a/tests/unit/converter/test_zalgo_converter.py b/tests/unit/prompt_converter/test_zalgo_converter.py similarity index 100% rename from tests/unit/converter/test_zalgo_converter.py rename to tests/unit/prompt_converter/test_zalgo_converter.py diff --git a/tests/unit/converter/test_zero_width_converter.py b/tests/unit/prompt_converter/test_zero_width_converter.py similarity index 100% rename from tests/unit/converter/test_zero_width_converter.py rename to tests/unit/prompt_converter/test_zero_width_converter.py