mirror of
https://github.com/lfnovo/open-notebook.git
synced 2026-04-28 11:30:00 +00:00
- Title preservation tests → test_graphs.py (TestSaveSourceTitlePreservation) - Source asset persistence tests → test_sources_api.py (new file) - Credential cascade delete tests → test_credentials_api.py (new file) - Delete test_bug_fixes.py
100 lines
3.3 KiB
Python
100 lines
3.3 KiB
Python
"""Tests for the credentials API endpoint."""
|
|
|
|
from unittest.mock import AsyncMock, patch
|
|
|
|
import pytest
|
|
from fastapi.testclient import TestClient
|
|
|
|
|
|
@pytest.fixture
|
|
def client():
|
|
"""Create test client after environment variables have been cleared by conftest."""
|
|
from api.main import app
|
|
|
|
return TestClient(app)
|
|
|
|
|
|
class TestCredentialCascadeDelete:
|
|
"""Tests for #651 - deleting credential cascade-deletes linked models."""
|
|
|
|
@pytest.mark.asyncio
|
|
@patch("api.routers.credentials.Credential.get")
|
|
async def test_cascade_delete_linked_models(self, mock_get, client):
|
|
"""Deleting credential without options cascade-deletes linked models."""
|
|
mock_model1 = AsyncMock()
|
|
mock_model1.id = "model:1"
|
|
mock_model1.provider = "openai"
|
|
mock_model1.name = "gpt-4"
|
|
|
|
mock_model2 = AsyncMock()
|
|
mock_model2.id = "model:2"
|
|
mock_model2.provider = "openai"
|
|
mock_model2.name = "gpt-3.5-turbo"
|
|
|
|
mock_cred = AsyncMock()
|
|
mock_cred.get_linked_models = AsyncMock(
|
|
return_value=[mock_model1, mock_model2]
|
|
)
|
|
mock_cred.delete = AsyncMock()
|
|
mock_get.return_value = mock_cred
|
|
|
|
response = client.delete("/api/credentials/cred:123")
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["deleted_models"] == 2
|
|
assert data["message"] == "Credential deleted successfully"
|
|
|
|
mock_model1.delete.assert_awaited_once()
|
|
mock_model2.delete.assert_awaited_once()
|
|
mock_cred.delete.assert_awaited_once()
|
|
|
|
@pytest.mark.asyncio
|
|
@patch("api.routers.credentials.Credential.get")
|
|
async def test_delete_credential_no_linked_models(self, mock_get, client):
|
|
"""Deleting credential with no linked models works cleanly."""
|
|
mock_cred = AsyncMock()
|
|
mock_cred.get_linked_models = AsyncMock(return_value=[])
|
|
mock_cred.delete = AsyncMock()
|
|
mock_get.return_value = mock_cred
|
|
|
|
response = client.delete("/api/credentials/cred:123")
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["deleted_models"] == 0
|
|
mock_cred.delete.assert_awaited_once()
|
|
|
|
@pytest.mark.asyncio
|
|
@patch("api.routers.credentials.Credential.get")
|
|
async def test_migrate_models_instead_of_delete(self, mock_get, client):
|
|
"""Passing migrate_to reassigns models instead of deleting them."""
|
|
mock_model = AsyncMock()
|
|
mock_model.id = "model:1"
|
|
mock_model.credential = "cred:123"
|
|
mock_model.save = AsyncMock()
|
|
|
|
mock_cred = AsyncMock()
|
|
mock_cred.get_linked_models = AsyncMock(return_value=[mock_model])
|
|
mock_cred.delete = AsyncMock()
|
|
|
|
mock_target_cred = AsyncMock()
|
|
mock_target_cred.id = "cred:456"
|
|
|
|
# First call returns cred to delete, second returns target
|
|
mock_get.side_effect = [mock_cred, mock_target_cred]
|
|
|
|
response = client.delete(
|
|
"/api/credentials/cred:123?migrate_to=cred:456"
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["deleted_models"] == 0 # Models were migrated, not deleted
|
|
mock_model.save.assert_awaited_once()
|
|
assert mock_model.credential == "cred:456"
|
|
mock_cred.delete.assert_awaited_once()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
pytest.main([__file__, "-v"])
|