Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions frontend/src/components/Scenarios/ScenarioDetail.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2023,7 +2023,8 @@ describe('ScenarioDetail', () => {
})

it.each(['user', 'unknown'])('warns before replacing %s data and confirms through Run preview', async (origin) => {
const user = userEvent.setup()
jest.useFakeTimers()
const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime })
jest.mocked(datasetsApi.listDatasets).mockResolvedValueOnce({ items: [{
name: 'harmbench', selection_key: 'dataset:named:harmbench', loaded: true, provider_available: true,
logical_examples: 8, origins: [origin], modalities: ['text'], harm_categories: [], has_unlabeled_harm_categories: false,
Expand All @@ -2035,7 +2036,9 @@ describe('ScenarioDetail', () => {
},
})
renderDetail('/scanner/foundry.red_team_agent')
await flushRenderedPromises()
await user.click(await screen.findByRole('radio', { name: 'Refresh' }))
await advanceTimers(300)
await user.click(screen.getByRole('button', { name: 'Launch scan' }))
const preview = await screen.findByRole('dialog', { name: 'Run preview' })
await waitFor(() => expect(within(preview).getByRole('button', { name: 'Launch scan' })).toBeEnabled())
Expand All @@ -2044,7 +2047,7 @@ describe('ScenarioDetail', () => {
expect(within(preview).getByRole('alert')).toHaveTextContent('prevent older runs from resuming')
await user.click(within(preview).getByRole('button', { name: 'Cancel' }))
expect(mockStartRun).not.toHaveBeenCalled()
await user.click(screen.getByRole('button', { name: 'Launch scan' }))
await user.click(await screen.findByRole('button', { name: 'Launch scan' }))
const confirmed = await screen.findByRole('dialog', { name: 'Run preview' })
await user.click(within(confirmed).getByRole('button', { name: 'Launch scan' }))
expect(mockStartRun).toHaveBeenCalledTimes(1)
Expand Down
9 changes: 4 additions & 5 deletions pyrit/converter/insert_punctuation_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,11 +187,10 @@ def _insert_within_words(self, prompt: str, num_insertions: int, punctuation_lis
# List of chars in the prompt string
prompt_list = list(prompt)
rng = self._get_random_generator(stream="punctuation")
# Store random indices of prompt_list into insert_indices
# If the prompt has only 0 or 1 chars, insert at the end of the prompt
insert_indices = (
[1] if len(prompt_list) <= num_insertions else rng.sample(range(len(prompt_list) - 1), num_insertions)
)
# Store random indices of prompt_list into insert_indices. A list of n
# characters has n + 1 insertion points (before the first character
# through after the last), so every position stays reachable.
insert_indices = rng.sample(range(len(prompt_list) + 1), num_insertions)

for index in insert_indices:
# Insert into prompt_list at the insert_indices with random punctuation from the punctuation_list
Expand Down
29 changes: 29 additions & 0 deletions tests/unit/converter/test_insert_punctuation_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# Licensed under the MIT license.

import re
from unittest.mock import patch

import pytest

Expand Down Expand Up @@ -78,3 +79,31 @@ async def test_punctuation_list_rejects_substrings_of_string_punctuation(punctua
converter = InsertPunctuationConverter(seed=1)
with pytest.raises(ValueError, match="Invalid punctuations"):
await converter.convert_async(prompt="hello big world", punctuation_list=punctuation_list)


@pytest.mark.parametrize(
("prompt", "offset"),
[("a", 0), ("a", 1), ("abc", 0), ("abc", 1), ("abc", 2), ("abc", 3)],
)
async def test_insert_within_words_reaches_every_insertion_position_async(*, prompt: str, offset: int) -> None:
converter = InsertPunctuationConverter(between_words=False, word_swap_ratio=1.0, seed=1)
with patch.object(converter, "_get_random_generator") as get_rng:
rng = get_rng.return_value
rng.sample.return_value = [offset]
rng.choice.return_value = "!"
result = await converter.convert_async(prompt=prompt, punctuation_list=["!"])

population, count = rng.sample.call_args.args
assert list(population) == list(range(len(prompt) + 1))
assert count == 1
assert result.output_text == f"{prompt[:offset]}!{prompt[offset:]}"
assert result.output_type == "text"


@pytest.mark.parametrize("prompt", ["", "...", " \t\n"])
async def test_insert_within_words_without_words_async(prompt: str) -> None:
converter = InsertPunctuationConverter(between_words=False, word_swap_ratio=1.0, seed=1)
result = await converter.convert_async(prompt=prompt, punctuation_list=["!"])

assert result.output_text == f"!{prompt}"
assert result.output_type == "text"
Loading