2026-03-23 14:25:47 +08:00
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
from datetime import datetime, timezone
|
|
|
|
|
from typing import Any, cast
|
|
|
|
|
from uuid import uuid4
|
|
|
|
|
|
|
|
|
|
import pytest
|
|
|
|
|
|
2026-03-24 12:38:11 +08:00
|
|
|
from models.automation_jobs import ScheduleType
|
2026-03-23 14:25:47 +08:00
|
|
|
from v1.auth.registration_bootstrap import (
|
|
|
|
|
compute_next_local_time_utc,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_compute_next_local_time_utc_from_asia_shanghai() -> None:
|
|
|
|
|
now_utc = datetime(2026, 3, 23, 0, 30, tzinfo=timezone.utc)
|
|
|
|
|
|
|
|
|
|
run_at, next_run_at = compute_next_local_time_utc(
|
|
|
|
|
now_utc=now_utc,
|
|
|
|
|
timezone_name="Asia/Shanghai",
|
|
|
|
|
local_hour=8,
|
|
|
|
|
local_minute=0,
|
2026-03-24 12:38:11 +08:00
|
|
|
schedule_type=ScheduleType.DAILY,
|
2026-03-23 14:25:47 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert run_at == datetime(2026, 3, 24, 0, 0, tzinfo=timezone.utc)
|
|
|
|
|
assert next_run_at == datetime(2026, 3, 25, 0, 0, tzinfo=timezone.utc)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_compute_next_local_time_utc_rolls_to_next_day_when_passed() -> None:
|
|
|
|
|
now_utc = datetime(2026, 3, 23, 2, 30, tzinfo=timezone.utc)
|
|
|
|
|
|
|
|
|
|
run_at, next_run_at = compute_next_local_time_utc(
|
|
|
|
|
now_utc=now_utc,
|
|
|
|
|
timezone_name="Asia/Shanghai",
|
|
|
|
|
local_hour=8,
|
|
|
|
|
local_minute=0,
|
2026-03-24 12:38:11 +08:00
|
|
|
schedule_type=ScheduleType.DAILY,
|
2026-03-23 14:25:47 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert run_at == datetime(2026, 3, 24, 0, 0, tzinfo=timezone.utc)
|
|
|
|
|
assert next_run_at == datetime(2026, 3, 25, 0, 0, tzinfo=timezone.utc)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_registration_service_is_idempotent_when_job_exists() -> None:
|
|
|
|
|
from v1.auth.registration_bootstrap import RegistrationAutomationBootstrapService
|
|
|
|
|
|
|
|
|
|
expected_owner_id = uuid4()
|
|
|
|
|
|
|
|
|
|
class _Repo:
|
|
|
|
|
inserted = 0
|
|
|
|
|
upsert_calls = 0
|
|
|
|
|
|
|
|
|
|
async def get_profile_timezone(self, *, user_id):
|
|
|
|
|
assert user_id == expected_owner_id
|
|
|
|
|
return "Asia/Shanghai"
|
|
|
|
|
|
|
|
|
|
async def insert_bootstrap_automation_job_if_absent(self, **kwargs):
|
|
|
|
|
assert kwargs["owner_id"] == expected_owner_id
|
|
|
|
|
assert kwargs["bootstrap_key"] == "memory_extraction"
|
|
|
|
|
self.inserted += 1
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
async def upsert_initial_memory(self, **kwargs):
|
|
|
|
|
self.upsert_calls += 1
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
class _Session:
|
|
|
|
|
async def commit(self):
|
|
|
|
|
raise AssertionError("must not commit when already exists")
|
|
|
|
|
|
|
|
|
|
async def rollback(self):
|
|
|
|
|
raise AssertionError("must not rollback when no error")
|
|
|
|
|
|
|
|
|
|
service = RegistrationAutomationBootstrapService(
|
|
|
|
|
repository=cast(Any, _Repo()), session=cast(Any, _Session())
|
|
|
|
|
)
|
|
|
|
|
await service.ensure_user_automation_jobs(user_id=str(expected_owner_id))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_registration_service_creates_initial_memories_when_missing() -> None:
|
|
|
|
|
from v1.auth.registration_bootstrap import RegistrationAutomationBootstrapService
|
|
|
|
|
|
|
|
|
|
expected_owner_id = uuid4()
|
|
|
|
|
|
|
|
|
|
class _Repo:
|
|
|
|
|
async def get_profile_timezone(self, *, user_id):
|
|
|
|
|
assert user_id == expected_owner_id
|
|
|
|
|
return "Asia/Shanghai"
|
|
|
|
|
|
|
|
|
|
async def upsert_initial_memory(self, **kwargs):
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
async def insert_bootstrap_automation_job_if_absent(self, **kwargs):
|
|
|
|
|
_ = kwargs
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
class _Session:
|
|
|
|
|
committed = 0
|
|
|
|
|
|
|
|
|
|
async def commit(self):
|
|
|
|
|
self.committed += 1
|
|
|
|
|
|
|
|
|
|
async def rollback(self):
|
|
|
|
|
raise AssertionError("must not rollback when no error")
|
|
|
|
|
|
|
|
|
|
session = _Session()
|
|
|
|
|
service = RegistrationAutomationBootstrapService(
|
|
|
|
|
repository=cast(Any, _Repo()), session=cast(Any, session)
|
|
|
|
|
)
|
|
|
|
|
await service.ensure_user_automation_jobs(user_id=str(expected_owner_id))
|
|
|
|
|
|
|
|
|
|
assert session.committed == 1
|