fix: Improve duplicate flow name handling and add comprehensive tests (#8962)
* ✨ (flows.py): Improve flow naming logic to handle duplicate names more effectively and accurately 📝 (test_rename_flow_to_save.py): Add unit tests to ensure correct numbering and handling of duplicate flow names * [autofix.ci] apply automated fixes * ✨ (test_rename_flow_to_save.py): refactor test functions to remove unnecessary session parameter and improve code readability * 📝 (flows.py): improve regex pattern to extract numbers only from flows following a specific naming convention to avoid extracting numbers from the original flow name if it contains parentheses * [autofix.ci] apply automated fixes * 📝 (flows.py): improve comments for better readability and understanding of regex usage in extracting numbers from flow names --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
parent
bd8e3a162a
commit
c1d417adf2
2 changed files with 172 additions and 1 deletions
|
|
@ -83,7 +83,15 @@ async def _new_flow(
|
|||
)
|
||||
).all()
|
||||
if flows:
|
||||
extract_number = re.compile(r"\((\d+)\)$")
|
||||
# Use regex to extract numbers only from flows that follow the copy naming pattern:
|
||||
# "{original_name} ({number})"
|
||||
# This avoids extracting numbers from the original flow name if it naturally contains parentheses
|
||||
#
|
||||
# Examples:
|
||||
# - For flow "My Flow": matches "My Flow (1)", "My Flow (2)" → extracts 1, 2
|
||||
# - For flow "Analytics (Q1)": matches "Analytics (Q1) (1)" → extracts 1
|
||||
# but does NOT match "Analytics (Q1)" → avoids extracting the original "1"
|
||||
extract_number = re.compile(rf"^{re.escape(flow.name)} \((\d+)\)$")
|
||||
numbers = []
|
||||
for _flow in flows:
|
||||
result = extract_number.search(_flow.name)
|
||||
|
|
@ -91,6 +99,8 @@ async def _new_flow(
|
|||
numbers.append(int(result.groups(1)[0]))
|
||||
if numbers:
|
||||
flow.name = f"{flow.name} ({max(numbers) + 1})"
|
||||
else:
|
||||
flow.name = f"{flow.name} (1)"
|
||||
else:
|
||||
flow.name = f"{flow.name} (1)"
|
||||
# Now check if the endpoint is unique
|
||||
|
|
|
|||
161
src/backend/tests/unit/api/v1/test_rename_flow_to_save.py
Normal file
161
src/backend/tests/unit/api/v1/test_rename_flow_to_save.py
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
import pytest
|
||||
from fastapi import status
|
||||
from httpx import AsyncClient
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_duplicate_flow_name_basic(client: AsyncClient, logged_in_headers):
|
||||
"""Test that duplicate flow names get numbered correctly."""
|
||||
base_flow = {
|
||||
"name": "Test Flow",
|
||||
"description": "Test flow description",
|
||||
"data": {},
|
||||
"is_component": False,
|
||||
}
|
||||
|
||||
# Create first flow
|
||||
response1 = await client.post("api/v1/flows/", json=base_flow, headers=logged_in_headers)
|
||||
assert response1.status_code == status.HTTP_201_CREATED
|
||||
assert response1.json()["name"] == "Test Flow"
|
||||
|
||||
# Create second flow with same name - should become "Test Flow (1)"
|
||||
response2 = await client.post("api/v1/flows/", json=base_flow, headers=logged_in_headers)
|
||||
assert response2.status_code == status.HTTP_201_CREATED
|
||||
assert response2.json()["name"] == "Test Flow (1)"
|
||||
|
||||
# Create third flow with same name - should become "Test Flow (2)"
|
||||
response3 = await client.post("api/v1/flows/", json=base_flow, headers=logged_in_headers)
|
||||
assert response3.status_code == status.HTTP_201_CREATED
|
||||
assert response3.json()["name"] == "Test Flow (2)"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_duplicate_flow_name_with_numbers_in_original(client: AsyncClient, logged_in_headers):
|
||||
"""Test duplication of flows with numbers in their original name."""
|
||||
base_flow = {
|
||||
"name": "Untitled document (7)",
|
||||
"description": "Test flow description",
|
||||
"data": {},
|
||||
"is_component": False,
|
||||
}
|
||||
|
||||
# Create first flow
|
||||
response1 = await client.post("api/v1/flows/", json=base_flow, headers=logged_in_headers)
|
||||
assert response1.status_code == status.HTTP_201_CREATED
|
||||
assert response1.json()["name"] == "Untitled document (7)"
|
||||
|
||||
# Create second flow with same name - should become "Untitled document (7) (1)"
|
||||
response2 = await client.post("api/v1/flows/", json=base_flow, headers=logged_in_headers)
|
||||
assert response2.status_code == status.HTTP_201_CREATED
|
||||
assert response2.json()["name"] == "Untitled document (7) (1)"
|
||||
|
||||
# Create third flow with same name - should become "Untitled document (7) (2)"
|
||||
response3 = await client.post("api/v1/flows/", json=base_flow, headers=logged_in_headers)
|
||||
assert response3.status_code == status.HTTP_201_CREATED
|
||||
assert response3.json()["name"] == "Untitled document (7) (2)"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_duplicate_flow_name_with_non_numeric_suffixes(client: AsyncClient, logged_in_headers):
|
||||
"""Test that non-numeric suffixes don't interfere with numbering."""
|
||||
base_flow = {
|
||||
"name": "My Flow",
|
||||
"description": "Test flow description",
|
||||
"data": {},
|
||||
"is_component": False,
|
||||
}
|
||||
|
||||
# Create first flow
|
||||
response1 = await client.post("api/v1/flows/", json=base_flow, headers=logged_in_headers)
|
||||
assert response1.status_code == status.HTTP_201_CREATED
|
||||
assert response1.json()["name"] == "My Flow"
|
||||
|
||||
# Create flow with non-numeric suffix
|
||||
backup_flow = base_flow.copy()
|
||||
backup_flow["name"] = "My Flow (Backup)"
|
||||
response2 = await client.post("api/v1/flows/", json=backup_flow, headers=logged_in_headers)
|
||||
assert response2.status_code == status.HTTP_201_CREATED
|
||||
assert response2.json()["name"] == "My Flow (Backup)"
|
||||
|
||||
# Create another flow with original name - should become "My Flow (1)"
|
||||
# because "My Flow (Backup)" doesn't match the numeric pattern
|
||||
response3 = await client.post("api/v1/flows/", json=base_flow, headers=logged_in_headers)
|
||||
assert response3.status_code == status.HTTP_201_CREATED
|
||||
assert response3.json()["name"] == "My Flow (1)"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_duplicate_flow_name_gaps_in_numbering(client: AsyncClient, logged_in_headers):
|
||||
"""Test that gaps in numbering are handled correctly (uses max + 1)."""
|
||||
base_flow = {
|
||||
"name": "Gapped Flow",
|
||||
"description": "Test flow description",
|
||||
"data": {},
|
||||
"is_component": False,
|
||||
}
|
||||
|
||||
# Create original flow
|
||||
response1 = await client.post("api/v1/flows/", json=base_flow, headers=logged_in_headers)
|
||||
assert response1.status_code == status.HTTP_201_CREATED
|
||||
assert response1.json()["name"] == "Gapped Flow"
|
||||
|
||||
# Create numbered flows with gaps
|
||||
numbered_flows = [
|
||||
"Gapped Flow (1)",
|
||||
"Gapped Flow (5)", # Gap: 2, 3, 4 missing
|
||||
"Gapped Flow (7)", # Gap: 6 missing
|
||||
]
|
||||
|
||||
for flow_name in numbered_flows:
|
||||
numbered_flow = base_flow.copy()
|
||||
numbered_flow["name"] = flow_name
|
||||
response = await client.post("api/v1/flows/", json=numbered_flow, headers=logged_in_headers)
|
||||
assert response.status_code == status.HTTP_201_CREATED
|
||||
assert response.json()["name"] == flow_name
|
||||
|
||||
# Create another duplicate - should use max(1,5,7) + 1 = 8
|
||||
response_final = await client.post("api/v1/flows/", json=base_flow, headers=logged_in_headers)
|
||||
assert response_final.status_code == status.HTTP_201_CREATED
|
||||
assert response_final.json()["name"] == "Gapped Flow (8)"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_duplicate_flow_name_special_characters(client: AsyncClient, logged_in_headers):
|
||||
"""Test duplication with special characters in flow names."""
|
||||
base_flow = {
|
||||
"name": "Flow-with_special@chars!",
|
||||
"description": "Test flow description",
|
||||
"data": {},
|
||||
"is_component": False,
|
||||
}
|
||||
|
||||
# Create first flow
|
||||
response1 = await client.post("api/v1/flows/", json=base_flow, headers=logged_in_headers)
|
||||
assert response1.status_code == status.HTTP_201_CREATED
|
||||
assert response1.json()["name"] == "Flow-with_special@chars!"
|
||||
|
||||
# Create duplicate - should properly escape special characters in regex
|
||||
response2 = await client.post("api/v1/flows/", json=base_flow, headers=logged_in_headers)
|
||||
assert response2.status_code == status.HTTP_201_CREATED
|
||||
assert response2.json()["name"] == "Flow-with_special@chars! (1)"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_duplicate_flow_name_regex_patterns(client: AsyncClient, logged_in_headers):
|
||||
"""Test that flow names containing regex special characters work correctly."""
|
||||
base_flow = {
|
||||
"name": "Flow (.*) [test]",
|
||||
"description": "Test flow description",
|
||||
"data": {},
|
||||
"is_component": False,
|
||||
}
|
||||
|
||||
# Create first flow
|
||||
response1 = await client.post("api/v1/flows/", json=base_flow, headers=logged_in_headers)
|
||||
assert response1.status_code == status.HTTP_201_CREATED
|
||||
assert response1.json()["name"] == "Flow (.*) [test]"
|
||||
|
||||
# Create duplicate
|
||||
response2 = await client.post("api/v1/flows/", json=base_flow, headers=logged_in_headers)
|
||||
assert response2.status_code == status.HTTP_201_CREATED
|
||||
assert response2.json()["name"] == "Flow (.*) [test] (1)"
|
||||
Loading…
Add table
Add a link
Reference in a new issue