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:
Cristhian Zanforlin Lousa 2025-07-22 14:45:15 -03:00 committed by GitHub
commit c1d417adf2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 172 additions and 1 deletions

View file

@ -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

View 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)"