Skip to content

Commit 029859c

Browse files
committed
test: add unit tests for cache, job queue, jobs, session, state, and auth services
1 parent 8d01ba4 commit 029859c

14 files changed

Lines changed: 1534 additions & 0 deletions
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
"""Tests for langflow.services.auth.exceptions module."""
2+
3+
import pytest
4+
5+
from langflow.services.auth.exceptions import (
6+
AuthenticationError,
7+
InactiveUserError,
8+
InsufficientPermissionsError,
9+
InvalidCredentialsError,
10+
InvalidTokenError,
11+
MissingCredentialsError,
12+
TokenExpiredError,
13+
)
14+
15+
16+
class TestAuthenticationError:
17+
def test_message(self):
18+
exc = AuthenticationError("test error")
19+
assert exc.message == "test error"
20+
assert str(exc) == "test error"
21+
22+
def test_error_code(self):
23+
exc = AuthenticationError("test", error_code="my_code")
24+
assert exc.error_code == "my_code"
25+
26+
def test_no_error_code(self):
27+
exc = AuthenticationError("test")
28+
assert exc.error_code is None
29+
30+
def test_is_exception(self):
31+
exc = AuthenticationError("test")
32+
assert isinstance(exc, Exception)
33+
34+
def test_can_be_raised_and_caught(self):
35+
with pytest.raises(AuthenticationError, match="test error"):
36+
raise AuthenticationError("test error")
37+
38+
39+
class TestInvalidCredentialsError:
40+
def test_default_message(self):
41+
exc = InvalidCredentialsError()
42+
assert exc.message == "Invalid credentials provided"
43+
assert exc.error_code == "invalid_credentials"
44+
45+
def test_custom_message(self):
46+
exc = InvalidCredentialsError("Wrong password")
47+
assert exc.message == "Wrong password"
48+
assert exc.error_code == "invalid_credentials"
49+
50+
def test_is_authentication_error(self):
51+
assert issubclass(InvalidCredentialsError, AuthenticationError)
52+
53+
54+
class TestMissingCredentialsError:
55+
def test_default_message(self):
56+
exc = MissingCredentialsError()
57+
assert exc.message == "No credentials provided"
58+
assert exc.error_code == "missing_credentials"
59+
60+
def test_custom_message(self):
61+
exc = MissingCredentialsError("Token required")
62+
assert exc.message == "Token required"
63+
64+
65+
class TestInactiveUserError:
66+
def test_default_message(self):
67+
exc = InactiveUserError()
68+
assert exc.message == "User account is inactive"
69+
assert exc.error_code == "inactive_user"
70+
71+
72+
class TestInsufficientPermissionsError:
73+
def test_default_message(self):
74+
exc = InsufficientPermissionsError()
75+
assert exc.message == "Insufficient permissions"
76+
assert exc.error_code == "insufficient_permissions"
77+
78+
79+
class TestTokenExpiredError:
80+
def test_default_message(self):
81+
exc = TokenExpiredError()
82+
assert exc.message == "Authentication token has expired"
83+
assert exc.error_code == "token_expired"
84+
85+
86+
class TestInvalidTokenError:
87+
def test_default_message(self):
88+
exc = InvalidTokenError()
89+
assert exc.message == "Invalid authentication token"
90+
assert exc.error_code == "invalid_token"
91+
92+
93+
class TestExceptionHierarchy:
94+
def test_all_subclass_authentication_error(self):
95+
subclasses = [
96+
InvalidCredentialsError,
97+
MissingCredentialsError,
98+
InactiveUserError,
99+
InsufficientPermissionsError,
100+
TokenExpiredError,
101+
InvalidTokenError,
102+
]
103+
for cls in subclasses:
104+
assert issubclass(cls, AuthenticationError), f"{cls.__name__} should be a subclass"
105+
106+
def test_catch_base_catches_all(self):
107+
exceptions = [
108+
InvalidCredentialsError(),
109+
MissingCredentialsError(),
110+
InactiveUserError(),
111+
InsufficientPermissionsError(),
112+
TokenExpiredError(),
113+
InvalidTokenError(),
114+
]
115+
for exc in exceptions:
116+
try:
117+
raise exc
118+
except AuthenticationError:
119+
pass # Should be caught

src/backend/tests/unit/services/cache/__init__.py

Whitespace-only changes.
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
"""Tests for langflow.services.cache.disk.AsyncDiskCache."""
2+
3+
import asyncio
4+
import time
5+
6+
import pytest
7+
8+
from langflow.services.cache.disk import AsyncDiskCache
9+
10+
pytestmark = pytest.mark.asyncio
11+
12+
13+
@pytest.fixture
14+
def cache(tmp_path):
15+
"""Create a fresh AsyncDiskCache for each test."""
16+
c = AsyncDiskCache(str(tmp_path / "cache"), max_size=10, expiration_time=3600)
17+
return c
18+
19+
20+
@pytest.fixture
21+
def short_expiry_cache(tmp_path):
22+
"""Cache with very short expiration for testing expiry."""
23+
return AsyncDiskCache(str(tmp_path / "cache_exp"), max_size=10, expiration_time=0.1)
24+
25+
26+
class TestAsyncDiskCacheSetGet:
27+
async def test_set_and_get_string(self, cache):
28+
await cache.set("key1", "value1")
29+
result = await cache.get("key1")
30+
assert result == "value1"
31+
32+
async def test_set_and_get_dict(self, cache):
33+
data = {"name": "test", "count": 42}
34+
await cache.set("key1", data)
35+
result = await cache.get("key1")
36+
assert result == data
37+
38+
async def test_set_and_get_list(self, cache):
39+
data = [1, 2, 3, "four"]
40+
await cache.set("key1", data)
41+
result = await cache.get("key1")
42+
assert result == data
43+
44+
async def test_set_and_get_int(self, cache):
45+
await cache.set("key1", 42)
46+
result = await cache.get("key1")
47+
assert result == 42
48+
49+
async def test_get_missing_key(self, cache):
50+
from lfx.services.cache.utils import CACHE_MISS
51+
52+
result = await cache.get("nonexistent")
53+
assert result is CACHE_MISS
54+
55+
56+
class TestAsyncDiskCacheDelete:
57+
async def test_delete_existing(self, cache):
58+
from lfx.services.cache.utils import CACHE_MISS
59+
60+
await cache.set("key1", "value1")
61+
await cache.delete("key1")
62+
result = await cache.get("key1")
63+
assert result is CACHE_MISS
64+
65+
async def test_delete_nonexistent(self, cache):
66+
# Should not raise
67+
await cache.delete("nonexistent")
68+
69+
70+
class TestAsyncDiskCacheClear:
71+
async def test_clear(self, cache):
72+
from lfx.services.cache.utils import CACHE_MISS
73+
74+
await cache.set("k1", "v1")
75+
await cache.set("k2", "v2")
76+
await cache.clear()
77+
assert await cache.get("k1") is CACHE_MISS
78+
assert await cache.get("k2") is CACHE_MISS
79+
80+
81+
class TestAsyncDiskCacheUpsert:
82+
# Note: We pass an explicit lock to avoid a deadlock in AsyncDiskCache._upsert
83+
# which calls self.set() without passing the lock, causing it to re-acquire self.lock.
84+
85+
async def test_upsert_new_key(self, cache):
86+
lock = asyncio.Lock()
87+
await cache.upsert("key1", "value1", lock=lock)
88+
result = await cache.get("key1")
89+
assert result == "value1"
90+
91+
async def test_upsert_merge_dicts(self, cache):
92+
lock = asyncio.Lock()
93+
await cache.set("key1", {"a": 1, "b": 2})
94+
await cache.upsert("key1", {"b": 3, "c": 4}, lock=lock)
95+
result = await cache.get("key1")
96+
assert result == {"a": 1, "b": 3, "c": 4}
97+
98+
async def test_upsert_replace_non_dict(self, cache):
99+
lock = asyncio.Lock()
100+
await cache.set("key1", "old_value")
101+
await cache.upsert("key1", "new_value", lock=lock)
102+
result = await cache.get("key1")
103+
assert result == "new_value"
104+
105+
106+
class TestAsyncDiskCacheContains:
107+
async def test_contains_existing(self, cache):
108+
await cache.set("key1", "v")
109+
assert await cache.contains("key1") is True
110+
111+
async def test_contains_missing(self, cache):
112+
assert await cache.contains("nonexistent") is False
113+
114+
115+
class TestAsyncDiskCacheExpiration:
116+
async def test_expired_item_returns_cache_miss(self, short_expiry_cache):
117+
from lfx.services.cache.utils import CACHE_MISS
118+
119+
await short_expiry_cache.set("key1", "value1")
120+
await asyncio.sleep(0.2) # Wait for expiration
121+
result = await short_expiry_cache.get("key1")
122+
assert result is CACHE_MISS
123+
124+
125+
class TestAsyncDiskCacheTeardown:
126+
async def test_teardown(self, cache):
127+
await cache.set("key1", "value1")
128+
await cache.teardown()
129+
# After teardown, cache should be cleared
130+
assert len(cache.cache) == 0
131+
132+
133+
class TestAsyncDiskCacheLocking:
134+
async def test_get_with_external_lock(self, cache):
135+
lock = asyncio.Lock()
136+
await cache.set("key1", "value1")
137+
async with lock:
138+
result = await cache.get("key1", lock=lock)
139+
assert result == "value1"
140+
141+
async def test_set_with_external_lock(self, cache):
142+
lock = asyncio.Lock()
143+
async with lock:
144+
await cache.set("key1", "value1", lock=lock)
145+
result = await cache.get("key1")
146+
assert result == "value1"

0 commit comments

Comments
 (0)