[feat] change str id type to UUID
This commit is contained in:
parent
e629e34c4d
commit
770af1fe76
11 changed files with 150 additions and 173 deletions
|
|
@ -1,18 +1,28 @@
|
|||
import threading
|
||||
from datetime import UTC, datetime, timedelta
|
||||
from uuid import UUID
|
||||
|
||||
from domain.sandbox import SandboxSession, SandboxStatus
|
||||
from repository.sandbox_lock import ProcessLocalSandboxLifecycleLocker
|
||||
from repository.sandbox_session import InMemorySandboxSessionRepository
|
||||
from usecase.sandbox import CleanupExpiredSandboxes, CreateSandbox, CreateSandboxCommand
|
||||
|
||||
CHAT_ID = '11111111-1111-1111-1111-111111111111'
|
||||
CHAT_ID = UUID('11111111-1111-1111-1111-111111111111')
|
||||
NON_CANONICAL_CHAT_ID = '11111111111111111111111111111111'
|
||||
EXPIRED_CHAT_ID = '22222222-2222-2222-2222-222222222222'
|
||||
BOUNDARY_CHAT_ID = '33333333-3333-3333-3333-333333333333'
|
||||
ACTIVE_CHAT_ID = '44444444-4444-4444-4444-444444444444'
|
||||
FAIL_CHAT_ID = '55555555-5555-5555-5555-555555555555'
|
||||
CLEAN_CHAT_ID = '66666666-6666-6666-6666-666666666666'
|
||||
EXPIRED_CHAT_ID = UUID('22222222-2222-2222-2222-222222222222')
|
||||
BOUNDARY_CHAT_ID = UUID('33333333-3333-3333-3333-333333333333')
|
||||
ACTIVE_CHAT_ID = UUID('44444444-4444-4444-4444-444444444444')
|
||||
FAIL_CHAT_ID = UUID('55555555-5555-5555-5555-555555555555')
|
||||
CLEAN_CHAT_ID = UUID('66666666-6666-6666-6666-666666666666')
|
||||
SESSION_REUSED_ID = UUID('00000000-0000-0000-0000-000000000001')
|
||||
SESSION_OLD_ID = UUID('00000000-0000-0000-0000-000000000002')
|
||||
SESSION_NEW_ID = UUID('00000000-0000-0000-0000-000000000003')
|
||||
SESSION_EXPIRED_ID = UUID('00000000-0000-0000-0000-000000000004')
|
||||
SESSION_BOUNDARY_ID = UUID('00000000-0000-0000-0000-000000000005')
|
||||
SESSION_ACTIVE_ID = UUID('00000000-0000-0000-0000-000000000006')
|
||||
SESSION_FAIL_ID = UUID('00000000-0000-0000-0000-000000000007')
|
||||
SESSION_CLEAN_ID = UUID('00000000-0000-0000-0000-000000000008')
|
||||
SESSION_REPLACEMENT_ID = UUID('00000000-0000-0000-0000-000000000009')
|
||||
|
||||
|
||||
class FakeClock:
|
||||
|
|
@ -52,9 +62,9 @@ class FakeLockContext:
|
|||
|
||||
class FakeLocker:
|
||||
def __init__(self) -> None:
|
||||
self.chat_ids: list[str] = []
|
||||
self.chat_ids: list[UUID] = []
|
||||
|
||||
def lock(self, chat_id: str) -> FakeLockContext:
|
||||
def lock(self, chat_id: UUID) -> FakeLockContext:
|
||||
self.chat_ids.append(chat_id)
|
||||
return FakeLockContext()
|
||||
|
||||
|
|
@ -63,7 +73,7 @@ class TrackingLockContext:
|
|||
def __init__(
|
||||
self,
|
||||
locker: 'TrackingLocker',
|
||||
chat_id: str,
|
||||
chat_id: UUID,
|
||||
inner_context,
|
||||
) -> None:
|
||||
self._locker = locker
|
||||
|
|
@ -89,9 +99,9 @@ class TrackingLocker:
|
|||
self._state_lock = threading.Lock()
|
||||
self._attempts = 0
|
||||
self.second_attempted = threading.Event()
|
||||
self.chat_ids: list[str] = []
|
||||
self.chat_ids: list[UUID] = []
|
||||
|
||||
def lock(self, chat_id: str) -> TrackingLockContext:
|
||||
def lock(self, chat_id: UUID) -> TrackingLockContext:
|
||||
return TrackingLockContext(self, chat_id, self._locker.lock(chat_id))
|
||||
|
||||
|
||||
|
|
@ -105,8 +115,8 @@ class BlockingCreateRuntime:
|
|||
def create(
|
||||
self,
|
||||
*,
|
||||
session_id: str,
|
||||
chat_id: str,
|
||||
session_id: UUID,
|
||||
chat_id: UUID,
|
||||
created_at: datetime,
|
||||
expires_at: datetime,
|
||||
) -> SandboxSession:
|
||||
|
|
@ -150,8 +160,8 @@ class FakeRuntime:
|
|||
def create(
|
||||
self,
|
||||
*,
|
||||
session_id: str,
|
||||
chat_id: str,
|
||||
session_id: UUID,
|
||||
chat_id: UUID,
|
||||
created_at: datetime,
|
||||
expires_at: datetime,
|
||||
) -> SandboxSession:
|
||||
|
|
@ -190,7 +200,7 @@ class FailingStopRuntime(FakeRuntime):
|
|||
def test_create_sandbox_reuses_active_session_when_not_expired() -> None:
|
||||
now = datetime(2026, 4, 2, 12, 0, tzinfo=UTC)
|
||||
session = SandboxSession(
|
||||
session_id='session-1',
|
||||
session_id=SESSION_REUSED_ID,
|
||||
chat_id=CHAT_ID,
|
||||
container_id='container-1',
|
||||
status=SandboxStatus.RUNNING,
|
||||
|
|
@ -223,8 +233,8 @@ def test_create_sandbox_reuses_active_session_when_not_expired() -> None:
|
|||
'info',
|
||||
'sandbox_reused',
|
||||
{
|
||||
'chat_id': CHAT_ID,
|
||||
'session_id': 'session-1',
|
||||
'chat_id': str(CHAT_ID),
|
||||
'session_id': str(SESSION_REUSED_ID),
|
||||
'container_id': 'container-1',
|
||||
},
|
||||
)
|
||||
|
|
@ -236,7 +246,7 @@ def test_create_sandbox_replaces_expired_session_and_creates_new_one(
|
|||
) -> None:
|
||||
now = datetime(2026, 4, 2, 12, 0, tzinfo=UTC)
|
||||
expired_session = SandboxSession(
|
||||
session_id='session-old',
|
||||
session_id=SESSION_OLD_ID,
|
||||
chat_id=CHAT_ID,
|
||||
container_id='container-old',
|
||||
status=SandboxStatus.RUNNING,
|
||||
|
|
@ -256,23 +266,23 @@ def test_create_sandbox_replaces_expired_session_and_creates_new_one(
|
|||
logger=logger,
|
||||
ttl=timedelta(minutes=5),
|
||||
)
|
||||
monkeypatch.setattr('usecase.sandbox._new_session_id', lambda: 'session-new')
|
||||
monkeypatch.setattr('usecase.sandbox._new_session_id', lambda: SESSION_NEW_ID)
|
||||
|
||||
result = usecase.execute(CreateSandboxCommand(chat_id=CHAT_ID))
|
||||
|
||||
assert runtime.stop_calls == ['container-old']
|
||||
assert runtime.create_calls == [
|
||||
{
|
||||
'session_id': 'session-new',
|
||||
'session_id': SESSION_NEW_ID,
|
||||
'chat_id': CHAT_ID,
|
||||
'created_at': now,
|
||||
'expires_at': now + timedelta(minutes=5),
|
||||
}
|
||||
]
|
||||
assert result == SandboxSession(
|
||||
session_id='session-new',
|
||||
session_id=SESSION_NEW_ID,
|
||||
chat_id=CHAT_ID,
|
||||
container_id='container-session-new',
|
||||
container_id=f'container-{SESSION_NEW_ID}',
|
||||
status=SandboxStatus.RUNNING,
|
||||
created_at=now,
|
||||
expires_at=now + timedelta(minutes=5),
|
||||
|
|
@ -284,8 +294,8 @@ def test_create_sandbox_replaces_expired_session_and_creates_new_one(
|
|||
'info',
|
||||
'sandbox_replaced',
|
||||
{
|
||||
'chat_id': CHAT_ID,
|
||||
'session_id': 'session-old',
|
||||
'chat_id': str(CHAT_ID),
|
||||
'session_id': str(SESSION_OLD_ID),
|
||||
'container_id': 'container-old',
|
||||
},
|
||||
),
|
||||
|
|
@ -293,9 +303,9 @@ def test_create_sandbox_replaces_expired_session_and_creates_new_one(
|
|||
'info',
|
||||
'sandbox_created',
|
||||
{
|
||||
'chat_id': CHAT_ID,
|
||||
'session_id': 'session-new',
|
||||
'container_id': 'container-session-new',
|
||||
'chat_id': str(CHAT_ID),
|
||||
'session_id': str(SESSION_NEW_ID),
|
||||
'container_id': f'container-{SESSION_NEW_ID}',
|
||||
},
|
||||
),
|
||||
]
|
||||
|
|
@ -316,7 +326,7 @@ def test_create_sandbox_creates_new_session_when_none_exists() -> None:
|
|||
ttl=timedelta(minutes=5),
|
||||
)
|
||||
|
||||
result = usecase.execute(CreateSandboxCommand(chat_id=NON_CANONICAL_CHAT_ID))
|
||||
result = usecase.execute(CreateSandboxCommand(chat_id=UUID(NON_CANONICAL_CHAT_ID)))
|
||||
|
||||
assert result.chat_id == CHAT_ID
|
||||
assert result.container_id == f'container-{result.session_id}'
|
||||
|
|
@ -332,15 +342,14 @@ def test_create_sandbox_creates_new_session_when_none_exists() -> None:
|
|||
}
|
||||
assert runtime.stop_calls == []
|
||||
assert repository.get_active_by_chat_id(CHAT_ID) == result
|
||||
assert repository.get_active_by_chat_id(NON_CANONICAL_CHAT_ID) is None
|
||||
assert locker.chat_ids == [CHAT_ID]
|
||||
assert logger.messages == [
|
||||
(
|
||||
'info',
|
||||
'sandbox_created',
|
||||
{
|
||||
'chat_id': CHAT_ID,
|
||||
'session_id': result.session_id,
|
||||
'chat_id': str(CHAT_ID),
|
||||
'session_id': str(result.session_id),
|
||||
'container_id': result.container_id,
|
||||
},
|
||||
)
|
||||
|
|
@ -363,7 +372,7 @@ def test_create_sandbox_serializes_duplicate_concurrent_create_for_chat_id(
|
|||
logger=logger,
|
||||
ttl=timedelta(minutes=5),
|
||||
)
|
||||
monkeypatch.setattr('usecase.sandbox._new_session_id', lambda: 'session-new')
|
||||
monkeypatch.setattr('usecase.sandbox._new_session_id', lambda: SESSION_NEW_ID)
|
||||
|
||||
results: list[SandboxSession | None] = [None, None]
|
||||
errors: list[Exception] = []
|
||||
|
|
@ -392,9 +401,9 @@ def test_create_sandbox_serializes_duplicate_concurrent_create_for_chat_id(
|
|||
assert errors == []
|
||||
assert results[0] == results[1]
|
||||
assert results[0] == SandboxSession(
|
||||
session_id='session-new',
|
||||
session_id=SESSION_NEW_ID,
|
||||
chat_id=CHAT_ID,
|
||||
container_id='container-session-new',
|
||||
container_id=f'container-{SESSION_NEW_ID}',
|
||||
status=SandboxStatus.RUNNING,
|
||||
created_at=now,
|
||||
expires_at=now + timedelta(minutes=5),
|
||||
|
|
@ -408,18 +417,18 @@ def test_create_sandbox_serializes_duplicate_concurrent_create_for_chat_id(
|
|||
'info',
|
||||
'sandbox_created',
|
||||
{
|
||||
'chat_id': CHAT_ID,
|
||||
'session_id': 'session-new',
|
||||
'container_id': 'container-session-new',
|
||||
'chat_id': str(CHAT_ID),
|
||||
'session_id': str(SESSION_NEW_ID),
|
||||
'container_id': f'container-{SESSION_NEW_ID}',
|
||||
},
|
||||
),
|
||||
(
|
||||
'info',
|
||||
'sandbox_reused',
|
||||
{
|
||||
'chat_id': CHAT_ID,
|
||||
'session_id': 'session-new',
|
||||
'container_id': 'container-session-new',
|
||||
'chat_id': str(CHAT_ID),
|
||||
'session_id': str(SESSION_NEW_ID),
|
||||
'container_id': f'container-{SESSION_NEW_ID}',
|
||||
},
|
||||
),
|
||||
]
|
||||
|
|
@ -428,7 +437,7 @@ def test_create_sandbox_serializes_duplicate_concurrent_create_for_chat_id(
|
|||
def test_cleanup_expired_sandboxes_stops_and_deletes_only_expired_sessions() -> None:
|
||||
now = datetime(2026, 4, 2, 12, 0, tzinfo=UTC)
|
||||
expired_session = SandboxSession(
|
||||
session_id='session-expired',
|
||||
session_id=SESSION_EXPIRED_ID,
|
||||
chat_id=EXPIRED_CHAT_ID,
|
||||
container_id='container-expired',
|
||||
status=SandboxStatus.RUNNING,
|
||||
|
|
@ -436,7 +445,7 @@ def test_cleanup_expired_sandboxes_stops_and_deletes_only_expired_sessions() ->
|
|||
expires_at=now - timedelta(seconds=1),
|
||||
)
|
||||
boundary_session = SandboxSession(
|
||||
session_id='session-boundary',
|
||||
session_id=SESSION_BOUNDARY_ID,
|
||||
chat_id=BOUNDARY_CHAT_ID,
|
||||
container_id='container-boundary',
|
||||
status=SandboxStatus.RUNNING,
|
||||
|
|
@ -444,7 +453,7 @@ def test_cleanup_expired_sandboxes_stops_and_deletes_only_expired_sessions() ->
|
|||
expires_at=now,
|
||||
)
|
||||
active_session = SandboxSession(
|
||||
session_id='session-active',
|
||||
session_id=SESSION_ACTIVE_ID,
|
||||
chat_id=ACTIVE_CHAT_ID,
|
||||
container_id='container-active',
|
||||
status=SandboxStatus.RUNNING,
|
||||
|
|
@ -479,8 +488,8 @@ def test_cleanup_expired_sandboxes_stops_and_deletes_only_expired_sessions() ->
|
|||
'info',
|
||||
'sandbox_cleaned',
|
||||
{
|
||||
'chat_id': EXPIRED_CHAT_ID,
|
||||
'session_id': 'session-expired',
|
||||
'chat_id': str(EXPIRED_CHAT_ID),
|
||||
'session_id': str(SESSION_EXPIRED_ID),
|
||||
'container_id': 'container-expired',
|
||||
},
|
||||
),
|
||||
|
|
@ -488,8 +497,8 @@ def test_cleanup_expired_sandboxes_stops_and_deletes_only_expired_sessions() ->
|
|||
'info',
|
||||
'sandbox_cleaned',
|
||||
{
|
||||
'chat_id': BOUNDARY_CHAT_ID,
|
||||
'session_id': 'session-boundary',
|
||||
'chat_id': str(BOUNDARY_CHAT_ID),
|
||||
'session_id': str(SESSION_BOUNDARY_ID),
|
||||
'container_id': 'container-boundary',
|
||||
},
|
||||
),
|
||||
|
|
@ -499,7 +508,7 @@ def test_cleanup_expired_sandboxes_stops_and_deletes_only_expired_sessions() ->
|
|||
def test_cleanup_expired_sandboxes_skips_replaced_session_from_stale_snapshot() -> None:
|
||||
now = datetime(2026, 4, 2, 12, 0, tzinfo=UTC)
|
||||
expired_snapshot = SandboxSession(
|
||||
session_id='session-expired',
|
||||
session_id=SESSION_EXPIRED_ID,
|
||||
chat_id=CHAT_ID,
|
||||
container_id='container-expired',
|
||||
status=SandboxStatus.RUNNING,
|
||||
|
|
@ -507,7 +516,7 @@ def test_cleanup_expired_sandboxes_skips_replaced_session_from_stale_snapshot()
|
|||
expires_at=now - timedelta(seconds=1),
|
||||
)
|
||||
replacement_session = SandboxSession(
|
||||
session_id='session-new',
|
||||
session_id=SESSION_REPLACEMENT_ID,
|
||||
chat_id=CHAT_ID,
|
||||
container_id='container-new',
|
||||
status=SandboxStatus.RUNNING,
|
||||
|
|
@ -539,7 +548,7 @@ def test_cleanup_expired_sandboxes_skips_replaced_session_from_stale_snapshot()
|
|||
def test_cleanup_expired_sandboxes_continues_after_stop_failure() -> None:
|
||||
now = datetime(2026, 4, 2, 12, 0, tzinfo=UTC)
|
||||
failing_session = SandboxSession(
|
||||
session_id='session-fail',
|
||||
session_id=SESSION_FAIL_ID,
|
||||
chat_id=FAIL_CHAT_ID,
|
||||
container_id='container-fail',
|
||||
status=SandboxStatus.RUNNING,
|
||||
|
|
@ -547,7 +556,7 @@ def test_cleanup_expired_sandboxes_continues_after_stop_failure() -> None:
|
|||
expires_at=now - timedelta(minutes=1),
|
||||
)
|
||||
cleaned_session = SandboxSession(
|
||||
session_id='session-clean',
|
||||
session_id=SESSION_CLEAN_ID,
|
||||
chat_id=CLEAN_CHAT_ID,
|
||||
container_id='container-clean',
|
||||
status=SandboxStatus.RUNNING,
|
||||
|
|
@ -580,8 +589,8 @@ def test_cleanup_expired_sandboxes_continues_after_stop_failure() -> None:
|
|||
'error',
|
||||
'sandbox_clean_failed',
|
||||
{
|
||||
'chat_id': FAIL_CHAT_ID,
|
||||
'session_id': 'session-fail',
|
||||
'chat_id': str(FAIL_CHAT_ID),
|
||||
'session_id': str(SESSION_FAIL_ID),
|
||||
'container_id': 'container-fail',
|
||||
'error': 'RuntimeError',
|
||||
},
|
||||
|
|
@ -590,8 +599,8 @@ def test_cleanup_expired_sandboxes_continues_after_stop_failure() -> None:
|
|||
'info',
|
||||
'sandbox_cleaned',
|
||||
{
|
||||
'chat_id': CLEAN_CHAT_ID,
|
||||
'session_id': 'session-clean',
|
||||
'chat_id': str(CLEAN_CHAT_ID),
|
||||
'session_id': str(SESSION_CLEAN_ID),
|
||||
'container_id': 'container-clean',
|
||||
},
|
||||
),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue