Skip to content

Commit feec9bd

Browse files
test: Added a test for the context manager
1 parent 98777ff commit feec9bd

File tree

3 files changed

+93
-8
lines changed

3 files changed

+93
-8
lines changed

diracx-db/src/diracx/db/sql/utils/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
apply_search_filters,
77
apply_sort_constraints,
88
)
9-
from .functions import hash, substract_date, utcnow
9+
from .functions import DBStateAssertion, hash, substract_date, utcnow
1010
from .types import Column, DateNowColumn, EnumBackedBool, EnumColumn, NullColumn
1111

1212
__all__ = (
@@ -22,4 +22,5 @@
2222
"substract_date",
2323
"hash",
2424
"SQLDBUnavailableError",
25+
"DBStateAssertion",
2526
)

diracx-db/src/diracx/db/sql/utils/functions.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -117,8 +117,10 @@ def hash(code: str):
117117
return hashlib.sha256(code.encode()).hexdigest()
118118

119119

120-
class DBStateAssertation:
121-
"""Class to handler context where we should not raise any excepted error.
120+
class DBStateAssertion:
121+
"""Context manager to ensure the integrity of a database transaction,
122+
specifically by rolling back any changes if certain expected exceptions
123+
occur—and raising a standardized error to indicate the database is in a bad state.
122124
123125
Example:
124126
with DBStateAssertation(self.conn, [PilotNotFoundError, SecretNotFoundError]):
@@ -143,21 +145,21 @@ async def __aexit__(self, exc_type, exc_value, exc_tb):
143145
# No exception occurred
144146
return False
145147

146-
# If we get here, an error occured, so we rollback changes
148+
# If we get here, an error occurred, so we rollback changes
147149
await self.conn.rollback()
148150

149151
# Check if the exception is among the expected ones
150152
if any(issubclass(exc_type, exc) for exc in self.exceptions):
151153
logger.error(
152-
f"An error occured with the db, exception message: {exc_value}"
154+
f"An error occurred with the db, exception message: {exc_value}"
153155
)
154156
raise DBInBadStateError(
155157
"This error may NOT have been raised. "
156-
"Please report this error: https://github.com/DIRACGrid/diracx/issues"
158+
"Please report this at https://github.com/DIRACGrid/diracx/issues"
157159
) from exc_value
158160

159161
# Not an expected exception; raise an unexpected error
160162
raise DBInBadStateError(
161-
"This should NOT happen. A miscellaneous error occured. "
162-
"Please report this error: https://github.com/DIRACGrid/diracx/issues"
163+
f"Unexpected error ({exc_type.__name__}): {exc_value}. "
164+
"Please report this at https://github.com/DIRACGrid/diracx/issues"
163165
) from exc_value
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
from __future__ import annotations
2+
3+
import pytest
4+
from sqlalchemy.exc import IntegrityError
5+
from uuid_utils import uuid7
6+
7+
from diracx.db.exceptions import DBInBadStateError
8+
from diracx.db.sql.auth.db import AuthDB
9+
from diracx.db.sql.utils import DBStateAssertion
10+
11+
12+
@pytest.fixture
13+
async def auth_db(tmp_path):
14+
auth_db = AuthDB("sqlite+aiosqlite:///:memory:")
15+
async with auth_db.engine_context():
16+
async with auth_db.engine.begin() as conn:
17+
await conn.run_sync(auth_db.metadata.create_all)
18+
yield auth_db
19+
20+
21+
async def test_context_manager(auth_db: AuthDB):
22+
"""We will test with refresh tokens the context manager.
23+
24+
1. Insert a refresh token in the DB with a proper jti
25+
2. Insert a refresh token with another jti
26+
3. Insert a refresh token with the first jti, except the right error /!\
27+
4. Insert a refresh token with the first jti, except the wrong error /!\
28+
29+
1. and 2. should pass
30+
3. and 4. should raise DBInBadStateError
31+
32+
3. Saying that the expected error was raised.
33+
4. Saying that an unexpected error was raised.
34+
"""
35+
# Insert a refresh token details
36+
async with auth_db as auth_db:
37+
jti = uuid7()
38+
await auth_db.insert_refresh_token(
39+
jti,
40+
"subject",
41+
"scope",
42+
)
43+
44+
# Revoke the token
45+
async with auth_db as auth_db:
46+
47+
# No error should be raised
48+
async with DBStateAssertion(auth_db.conn, [IntegrityError]):
49+
await auth_db.insert_refresh_token(
50+
uuid7(),
51+
"subject",
52+
"scope",
53+
)
54+
55+
# Await the right error (IntegrityError)
56+
with pytest.raises(DBInBadStateError) as exc_info:
57+
# DBInBadStateError because we say that IntegrityError should not be raised
58+
async with DBStateAssertion(auth_db.conn, [IntegrityError]):
59+
await auth_db.insert_refresh_token(
60+
jti,
61+
"subject",
62+
"scope",
63+
)
64+
65+
assert (
66+
str(exc_info.value)
67+
== "This error may NOT have been raised. Please report this at https://github.com/DIRACGrid/diracx/issues"
68+
)
69+
70+
# Await the wrong error
71+
with pytest.raises(DBInBadStateError) as exc_info:
72+
# DBInBadStateError because we say that IntegrityError should not be raised
73+
async with DBStateAssertion(auth_db.conn, [ValueError]):
74+
await auth_db.insert_refresh_token(
75+
jti,
76+
"subject",
77+
"scope",
78+
)
79+
80+
# (We use "in" because the error details can change depending on the engine)
81+
assert str(exc_info.value).startswith("Unexpected error (IntegrityError):")
82+
assert "IntegrityError" in str(exc_info)

0 commit comments

Comments
 (0)