| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106 |
- """Tests for ``services/bambu_cloud_credentials`` — the credential seam.
- The read paths are covered indirectly by the cloud-token expiry and
- migration suites; these pin the write path that review blocker 5 hinged on:
- ``mark_cloud_token_invalid`` must record a rejection for *both* identity
- shapes, because auth-disabled single-user installs (the default) hold their
- token in global ``Settings`` — ``user_id=None`` is a real, expected input,
- not a degenerate one.
- """
- from __future__ import annotations
- from datetime import datetime
- import pytest
- from sqlalchemy import select
- from backend.app.core.auth import get_password_hash
- from backend.app.models.settings import Settings
- from backend.app.models.user import User
- from backend.app.services import bambu_cloud_credentials as creds
- from backend.app.services.bambu_cloud_credentials import (
- CLOUD_TOKEN_INVALID_KEY,
- mark_cloud_token_invalid,
- )
- pytestmark = pytest.mark.asyncio
- class _SharedSessionCtx:
- """Route ``mark`` through the fixture's in-memory session: the function
- normally opens its own session against the configured database, which in
- tests is a different SQLite than ``db_session``'s in-memory one."""
- def __init__(self, session):
- self._session = session
- async def __aenter__(self):
- return self._session
- async def __aexit__(self, *exc):
- return False
- @pytest.fixture(autouse=True)
- def shared_session(db_session, monkeypatch):
- monkeypatch.setattr(creds, "async_session", lambda: _SharedSessionCtx(db_session))
- async def _make_user(db, username: str = "cred-user") -> User:
- user = User(
- username=username,
- password_hash=get_password_hash("AdminPass1!"),
- role="admin",
- is_active=True,
- )
- db.add(user)
- await db.commit()
- await db.refresh(user)
- return user
- async def test_mark_sets_the_per_user_flag(db_session):
- """user_id set → the rejection lands on that user's column."""
- user = await _make_user(db_session)
- await mark_cloud_token_invalid(user.id)
- await db_session.refresh(user)
- assert user.cloud_token_invalid_at is not None
- async def test_mark_none_writes_the_global_settings_flag(db_session):
- """user_id=None (auth-disabled install) → the global ``Settings`` row.
- A second call updates the existing row rather than adding another."""
- await mark_cloud_token_invalid(None)
- result = await db_session.execute(select(Settings).where(Settings.key == CLOUD_TOKEN_INVALID_KEY))
- rows = result.scalars().all()
- assert len(rows) == 1
- # Stored value parses as ISO — the status endpoints compare it as a date.
- datetime.fromisoformat(rows[0].value)
- first_value = rows[0].value
- await mark_cloud_token_invalid(None)
- result = await db_session.execute(select(Settings).where(Settings.key == CLOUD_TOKEN_INVALID_KEY))
- rows = result.scalars().all()
- assert len(rows) == 1
- assert rows[0].value >= first_value
- async def test_mark_is_best_effort(db_session, monkeypatch):
- """A bookkeeping failure must never replace the 401 the caller needs to
- see — the function swallows everything."""
- class _Boom:
- async def __aenter__(self):
- raise RuntimeError("db gone")
- async def __aexit__(self, *exc):
- return False
- monkeypatch.setattr(creds, "async_session", lambda: _Boom())
- # Must not raise.
- await mark_cloud_token_invalid(None)
|