ComfyUI-Manager/tests/glob/test_installed_api_enabled_priority.py
Dr.Lt.Data b7a8f85530 test: fix test isolation in test_installed_api_no_duplicates_across_scenarios
Fix sequential scenario test that was failing in Env 9 due to improper
state management between scenarios.

Root Cause:
In scenario "CNR enabled + Nightly disabled":
1. Install Nightly → auto-disables CNR
2. Disable Nightly → both packages now disabled
3. Test expects: CNR enabled, Nightly disabled
4. Actual state: both disabled (CNR not re-enabled)

Fix:
Added enable operation after disabling Nightly to restore CNR to enabled state.
This ensures the scenario accurately represents "CNR enabled + Nightly disabled"
instead of leaving both packages disabled.

Changes:
- Added enable CNR operation after disabling Nightly in scenario 2
- Updated ui_id to be more descriptive (disable_nightly, enable_cnr)
- Ensures proper state transition: both enabled → Nightly disabled → CNR re-enabled

Test Results:
- Before: 9/10 environments passing (90%)
- After: 10/10 environments passing (100%)
- All 63 tests passing across all environments

Session Progress:
- Session start: 7/10 environments (60/63 tests)
- After parameter fix: 9/10 environments (62/63 tests)
- Final: 10/10 environments (63/63 tests )
- Total improvement: +3 environments, +3 tests (+42.9%)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-08 15:22:02 +09:00

503 lines
18 KiB
Python

"""
Test that /v2/customnode/installed API priority rules work correctly.
This test verifies that the `/v2/customnode/installed` API follows two priority rules:
Rule 1 (Enabled-Priority):
- When both enabled and disabled versions exist → Show ONLY enabled version
- Prevents frontend confusion from duplicate package entries
Rule 2 (CNR-Priority for disabled packages):
- When both CNR and Nightly are disabled → Show ONLY CNR version
- CNR stable releases take priority over development Nightly builds
Additional behaviors:
1. Only returns the enabled version when both enabled and disabled versions exist
2. Does not return duplicate entries for the same package
3. Returns disabled version only when no enabled version exists
4. When both are disabled, CNR version takes priority over Nightly
"""
import pytest
import requests
import time
from pathlib import Path
TEST_PACKAGE_ID = "ComfyUI_SigmoidOffsetScheduler"
WAIT_TIME_SHORT = 10
WAIT_TIME_MEDIUM = 30
@pytest.fixture
def setup_cnr_enabled_nightly_disabled(api_client, custom_nodes_path):
"""
Setup fixture: CNR v1.0.1 enabled, Nightly disabled.
This creates the scenario where both versions exist but in different states:
- custom_nodes/ComfyUI_SigmoidOffsetScheduler/ (CNR v1.0.1, enabled)
- .disabled/comfyui_sigmoidoffsetscheduler@nightly/ (Nightly, disabled)
"""
import shutil
# Clean up any existing package (session fixture may have restored CNR)
enabled_path = custom_nodes_path / TEST_PACKAGE_ID
disabled_path = custom_nodes_path / ".disabled"
if enabled_path.exists():
shutil.rmtree(enabled_path)
if disabled_path.exists():
for item in disabled_path.iterdir():
if 'sigmoid' in item.name.lower() and item.is_dir():
shutil.rmtree(item)
# Install Nightly version first
response = api_client.queue_task(
kind="install",
ui_id="setup_nightly_install",
params={
"id": TEST_PACKAGE_ID,
"version": "nightly",
"selected_version": "nightly",
},
)
assert response.status_code == 200, f"Failed to queue Nightly install: {response.text}"
response = api_client.start_queue()
assert response.status_code in [200, 201], f"Failed to start queue: {response.text}"
time.sleep(WAIT_TIME_MEDIUM)
# Verify Nightly is installed and enabled
enabled_path = custom_nodes_path / TEST_PACKAGE_ID
assert enabled_path.exists(), "Nightly should be enabled"
assert (enabled_path / ".git").exists(), "Nightly should have .git directory"
# Install CNR version (this will disable Nightly and enable CNR)
response = api_client.queue_task(
kind="install",
ui_id="setup_cnr_install",
params={
"id": TEST_PACKAGE_ID,
"version": "1.0.1",
"selected_version": "latest",
},
)
assert response.status_code == 200, f"Failed to queue CNR install: {response.text}"
response = api_client.start_queue()
assert response.status_code in [200, 201], f"Failed to start queue: {response.text}"
time.sleep(WAIT_TIME_MEDIUM)
# Verify final state: CNR enabled, Nightly disabled
assert enabled_path.exists(), "CNR should be enabled"
assert (enabled_path / ".tracking").exists(), "CNR should have .tracking marker"
disabled_path = custom_nodes_path / ".disabled"
disabled_nightly = [
item for item in disabled_path.iterdir()
if 'sigmoid' in item.name.lower() and (item / ".git").exists()
]
assert len(disabled_nightly) == 1, "Should have one disabled Nightly package"
yield
# Cleanup
# (cleanup handled by conftest.py session fixture)
def test_installed_api_shows_only_enabled_when_both_exist(
api_client,
server_url,
custom_nodes_path,
setup_cnr_enabled_nightly_disabled
):
"""
Test that /installed API only shows enabled package when both versions exist.
Setup:
- CNR v1.0.1 enabled in custom_nodes/ComfyUI_SigmoidOffsetScheduler/
- Nightly disabled in .disabled/comfyui_sigmoidoffsetscheduler@nightly/
Expected:
- /v2/customnode/installed returns ONLY the enabled CNR package
- No duplicate entry for the disabled Nightly version
- enabled: True for the CNR package
This prevents frontend confusion from seeing two entries for the same package.
"""
# Verify setup state on filesystem
enabled_path = custom_nodes_path / TEST_PACKAGE_ID
assert enabled_path.exists(), "CNR should be enabled"
disabled_path = custom_nodes_path / ".disabled"
disabled_packages = [
item for item in disabled_path.iterdir()
if 'sigmoid' in item.name.lower() and item.is_dir()
]
assert len(disabled_packages) > 0, "Should have at least one disabled package"
# Call /v2/customnode/installed API
response = requests.get(f"{server_url}/v2/customnode/installed")
assert response.status_code == 200, f"API call failed: {response.text}"
installed = response.json()
# Find all entries for our test package
sigmoid_entries = [
(key, info) for key, info in installed.items()
if 'sigmoid' in key.lower() or 'sigmoid' in info.get('cnr_id', '').lower()
]
# Critical assertion: Should have EXACTLY ONE entry, not two
assert len(sigmoid_entries) == 1, (
f"Expected exactly 1 entry in /installed API, but found {len(sigmoid_entries)}. "
f"This causes frontend confusion. Entries: {sigmoid_entries}"
)
# Verify the single entry is the enabled one
package_key, package_info = sigmoid_entries[0]
assert package_info['enabled'] is True, (
f"The single entry should be enabled=True, got: {package_info}"
)
# Verify it's the CNR version (has version number)
assert package_info['ver'].count('.') >= 2, (
f"Should be CNR version with semantic version, got: {package_info['ver']}"
)
def test_installed_api_shows_disabled_when_no_enabled_exists(
api_client,
server_url,
custom_nodes_path
):
"""
Test that /installed API shows disabled package when no enabled version exists.
Setup:
- Install and then disable a package (no other version exists)
Expected:
- /v2/customnode/installed returns the disabled package
- enabled: False
- Only one entry for the package
This verifies that disabled packages are still visible when they're the only version.
"""
# Install CNR version
response = api_client.queue_task(
kind="install",
ui_id="test_disabled_only_install",
params={
"id": TEST_PACKAGE_ID,
"version": "1.0.1",
"selected_version": "latest",
},
)
assert response.status_code == 200
response = api_client.start_queue()
assert response.status_code in [200, 201]
time.sleep(WAIT_TIME_MEDIUM)
# Disable it
response = api_client.queue_task(
kind="disable",
ui_id="test_disabled_only_disable",
params={"node_name": TEST_PACKAGE_ID},
)
assert response.status_code == 200
response = api_client.start_queue()
assert response.status_code in [200, 201]
time.sleep(WAIT_TIME_MEDIUM)
# Verify it's disabled on filesystem
enabled_path = custom_nodes_path / TEST_PACKAGE_ID
assert not enabled_path.exists(), "Package should be disabled"
disabled_path = custom_nodes_path / ".disabled"
disabled_packages = [
item for item in disabled_path.iterdir()
if 'sigmoid' in item.name.lower() and item.is_dir()
]
assert len(disabled_packages) > 0, "Should have disabled package"
# Call /v2/customnode/installed API
response = requests.get(f"{server_url}/v2/customnode/installed")
assert response.status_code == 200
installed = response.json()
# Find entry for our test package
sigmoid_entries = [
(key, info) for key, info in installed.items()
if 'sigmoid' in key.lower() or 'sigmoid' in info.get('cnr_id', '').lower()
]
# Should have exactly one entry (the disabled one)
assert len(sigmoid_entries) == 1, (
f"Expected exactly 1 entry for disabled-only package, found {len(sigmoid_entries)}"
)
# Verify it's marked as disabled
package_key, package_info = sigmoid_entries[0]
assert package_info['enabled'] is False, (
f"Package should be disabled, got: {package_info}"
)
# Cleanup: Re-enable package for other tests
response = api_client.queue_task(
kind="enable",
ui_id="test_disabled_only_cleanup",
params={"cnr_id": TEST_PACKAGE_ID},
)
assert response.status_code == 200
response = api_client.start_queue()
assert response.status_code in [200, 201]
time.sleep(WAIT_TIME_SHORT)
def test_installed_api_no_duplicates_across_scenarios(
api_client,
server_url,
custom_nodes_path
):
"""
Test that /installed API never returns duplicate entries regardless of scenario.
This test cycles through multiple scenarios:
1. CNR enabled only
2. CNR enabled + Nightly disabled
3. Nightly enabled + CNR disabled
4. Both disabled
In all cases, the API should return at most ONE entry per unique package.
"""
scenarios = [
("cnr_only", "CNR enabled only"),
("cnr_enabled_nightly_disabled", "CNR enabled + Nightly disabled"),
("nightly_enabled_cnr_disabled", "Nightly enabled + CNR disabled"),
]
for scenario_id, scenario_desc in scenarios:
# Setup scenario
if scenario_id == "cnr_only":
# Install CNR only
response = api_client.queue_task(
kind="install",
ui_id=f"test_{scenario_id}_install",
params={
"id": TEST_PACKAGE_ID,
"version": "1.0.1",
"selected_version": "latest",
},
)
assert response.status_code == 200
response = api_client.start_queue()
assert response.status_code in [200, 201]
time.sleep(WAIT_TIME_MEDIUM)
elif scenario_id == "cnr_enabled_nightly_disabled":
# Install Nightly (this auto-disables CNR), then disable Nightly, then enable CNR
response = api_client.queue_task(
kind="install",
ui_id=f"test_{scenario_id}_nightly",
params={
"id": TEST_PACKAGE_ID,
"version": "nightly",
"selected_version": "nightly",
},
)
assert response.status_code == 200
response = api_client.start_queue()
assert response.status_code in [200, 201]
time.sleep(WAIT_TIME_MEDIUM)
# Disable Nightly
response = api_client.queue_task(
kind="disable",
ui_id=f"test_{scenario_id}_disable_nightly",
params={"node_name": TEST_PACKAGE_ID},
)
assert response.status_code == 200
response = api_client.start_queue()
assert response.status_code in [200, 201]
time.sleep(WAIT_TIME_MEDIUM)
# Re-enable CNR (which was auto-disabled when Nightly was installed)
response = api_client.queue_task(
kind="enable",
ui_id=f"test_{scenario_id}_enable_cnr",
params={"cnr_id": TEST_PACKAGE_ID},
)
assert response.status_code == 200
response = api_client.start_queue()
assert response.status_code in [200, 201]
time.sleep(WAIT_TIME_MEDIUM)
elif scenario_id == "nightly_enabled_cnr_disabled":
# CNR should already be disabled from previous scenario
# Enable Nightly (install if not exists)
response = api_client.queue_task(
kind="install",
ui_id=f"test_{scenario_id}_nightly",
params={
"id": TEST_PACKAGE_ID,
"version": "nightly",
"selected_version": "nightly",
},
)
assert response.status_code == 200
response = api_client.start_queue()
assert response.status_code in [200, 201]
time.sleep(WAIT_TIME_MEDIUM)
# Call API and verify no duplicates
response = requests.get(f"{server_url}/v2/customnode/installed")
assert response.status_code == 200, f"API call failed for {scenario_desc}"
installed = response.json()
sigmoid_entries = [
(key, info) for key, info in installed.items()
if 'sigmoid' in key.lower() or 'sigmoid' in info.get('cnr_id', '').lower()
]
# Critical: Should never have more than one entry
assert len(sigmoid_entries) <= 1, (
f"Scenario '{scenario_desc}': Expected at most 1 entry, found {len(sigmoid_entries)}. "
f"Entries: {sigmoid_entries}"
)
if len(sigmoid_entries) == 1:
package_key, package_info = sigmoid_entries[0]
# If entry exists, it should be enabled=True
# (disabled-only case is covered in separate test)
if scenario_id != "all_disabled":
assert package_info['enabled'] is True, (
f"Scenario '{scenario_desc}': Entry should be enabled=True, got: {package_info}"
)
def test_installed_api_cnr_priority_when_both_disabled(
api_client,
server_url,
custom_nodes_path
):
"""
Test Rule 2 (CNR-Priority): When both CNR and Nightly are disabled, show ONLY CNR.
Setup:
- Install CNR v1.0.1 and disable it
- Install Nightly and disable it
- Both versions exist in .disabled/ directory
Expected:
- /v2/customnode/installed returns ONLY the CNR version
- CNR version has enabled: False
- Nightly version is NOT in the response
- This prevents confusion and prioritizes stable releases over dev builds
Rationale:
CNR versions are stable releases and should be preferred over development
Nightly builds when both are inactive. This gives users clear indication
of which version would be activated if they choose to enable.
"""
# Install CNR version first
response = api_client.queue_task(
kind="install",
ui_id="test_cnr_priority_cnr_install",
params={
"id": TEST_PACKAGE_ID,
"version": "1.0.1",
"selected_version": "latest",
},
)
assert response.status_code == 200
response = api_client.start_queue()
assert response.status_code in [200, 201]
time.sleep(WAIT_TIME_MEDIUM)
# Install Nightly (this will disable CNR)
response = api_client.queue_task(
kind="install",
ui_id="test_cnr_priority_nightly_install",
params={
"id": TEST_PACKAGE_ID,
"version": "nightly",
"selected_version": "nightly",
},
)
assert response.status_code == 200
response = api_client.start_queue()
assert response.status_code in [200, 201]
time.sleep(WAIT_TIME_MEDIUM)
# Disable Nightly (now both are disabled)
response = api_client.queue_task(
kind="disable",
ui_id="test_cnr_priority_nightly_disable",
params={"node_name": TEST_PACKAGE_ID},
)
assert response.status_code == 200
response = api_client.start_queue()
assert response.status_code in [200, 201]
time.sleep(WAIT_TIME_MEDIUM)
# Verify filesystem state: both should be in .disabled/
disabled_path = custom_nodes_path / ".disabled"
disabled_packages = [
item for item in disabled_path.iterdir()
if 'sigmoid' in item.name.lower() and item.is_dir()
]
# Should have both CNR and Nightly in .disabled/
cnr_disabled = [p for p in disabled_packages if (p / ".tracking").exists()]
nightly_disabled = [p for p in disabled_packages if (p / ".git").exists()]
assert len(cnr_disabled) >= 1, f"Should have disabled CNR package, found: {[p.name for p in disabled_packages]}"
assert len(nightly_disabled) >= 1, f"Should have disabled Nightly package, found: {[p.name for p in disabled_packages]}"
# Call /v2/customnode/installed API
response = requests.get(f"{server_url}/v2/customnode/installed")
assert response.status_code == 200
installed = response.json()
# Find all entries for our test package
sigmoid_entries = [
(key, info) for key, info in installed.items()
if 'sigmoid' in key.lower() or 'sigmoid' in info.get('cnr_id', '').lower()
]
# Critical assertion: Should have EXACTLY ONE entry (CNR), not two
assert len(sigmoid_entries) == 1, (
f"Rule 2 (CNR-Priority) violated: Expected exactly 1 entry (CNR only), "
f"but found {len(sigmoid_entries)}. Entries: {sigmoid_entries}"
)
# Verify the single entry is the CNR version
package_key, package_info = sigmoid_entries[0]
# Should be disabled
assert package_info['enabled'] is False, (
f"Package should be disabled, got: {package_info}"
)
# Should have cnr_id (CNR packages have cnr_id, Nightly has empty cnr_id)
assert package_info.get('cnr_id'), (
f"Should be CNR package with cnr_id, got: {package_info}"
)
# Should have null aux_id (CNR packages have aux_id=null, Nightly has aux_id set)
assert package_info.get('aux_id') is None, (
f"Should be CNR package with aux_id=null, got: {package_info}"
)
# Should have semantic version (CNR uses semver, Nightly uses git hash)
ver = package_info['ver']
assert ver.count('.') >= 2 or ver[0].isdigit(), (
f"Should be CNR with semantic version, got: {ver}"
)