"""Tests for manual pin / unpin on note versions.""" from unittest.mock import AsyncMock, MagicMock, patch def _mock_session_for_version(mock_version): mock_session = AsyncMock() result = MagicMock() result.scalars.return_value.first.return_value = mock_version mock_session.execute = AsyncMock(return_value=result) mock_session.commit = AsyncMock() mock_session.refresh = AsyncMock() mock_session.__aenter__ = AsyncMock(return_value=mock_session) mock_session.__aexit__ = AsyncMock(return_value=False) return mock_session async def test_pin_version_sets_manual_kind_and_label(): mock_version = MagicMock() mock_version.pin_kind = None mock_version.pin_label = None with patch( "scribe.services.version_pinning.async_session", return_value=_mock_session_for_version(mock_version), ): from scribe.services.version_pinning import pin_version result = await pin_version( user_id=1, note_id=42, version_id=7, label="the runbook circa Q2", ) assert result is mock_version assert mock_version.pin_kind == "manual" assert mock_version.pin_label == "the runbook circa Q2" async def test_pin_version_accepts_null_label(): """label is optional — pinning with no label is allowed.""" mock_version = MagicMock() mock_version.pin_kind = None mock_version.pin_label = None with patch( "scribe.services.version_pinning.async_session", return_value=_mock_session_for_version(mock_version), ): from scribe.services.version_pinning import pin_version await pin_version(user_id=1, note_id=42, version_id=7, label=None) assert mock_version.pin_kind == "manual" assert mock_version.pin_label is None async def test_pin_version_promotes_auto_to_manual_with_label_update(): """Pinning an already-auto-pinned version promotes it and updates label.""" mock_version = MagicMock() mock_version.pin_kind = "auto" mock_version.pin_label = "stable 2026-04-01 → 2026-04-05" with patch( "scribe.services.version_pinning.async_session", return_value=_mock_session_for_version(mock_version), ): from scribe.services.version_pinning import pin_version await pin_version( user_id=1, note_id=42, version_id=7, label="post-network-refresh", ) assert mock_version.pin_kind == "manual" assert mock_version.pin_label == "post-network-refresh" async def test_pin_version_rejects_overlong_label(): """Labels are capped at 500 chars to keep the UI sane.""" mock_version = MagicMock() mock_version.pin_kind = None # The cap is checked before any DB access, so the session shouldn't # even be entered. We still patch it to avoid accidental DB lookups # if the implementation order changes. with patch( "scribe.services.version_pinning.async_session", return_value=_mock_session_for_version(mock_version), ): from scribe.services.version_pinning import pin_version try: await pin_version( user_id=1, note_id=42, version_id=7, label="x" * 501, ) assert False, "expected ValueError" except ValueError as e: assert "500" in str(e) or "long" in str(e).lower() async def test_pin_version_returns_none_when_not_found(): mock_session = AsyncMock() result = MagicMock() result.scalars.return_value.first.return_value = None mock_session.execute = AsyncMock(return_value=result) mock_session.commit = AsyncMock() mock_session.refresh = AsyncMock() mock_session.__aenter__ = AsyncMock(return_value=mock_session) mock_session.__aexit__ = AsyncMock(return_value=False) with patch( "scribe.services.version_pinning.async_session", return_value=mock_session, ): from scribe.services.version_pinning import pin_version out = await pin_version(user_id=1, note_id=42, version_id=99, label=None) assert out is None async def test_unpin_version_clears_kind_and_label(): mock_version = MagicMock() mock_version.pin_kind = "manual" mock_version.pin_label = "previous label" with patch( "scribe.services.version_pinning.async_session", return_value=_mock_session_for_version(mock_version), ): from scribe.services.version_pinning import unpin_version result = await unpin_version(user_id=1, note_id=42, version_id=7) assert result is mock_version assert mock_version.pin_kind is None assert mock_version.pin_label is None