gabriel / musehub public
test_musehub_sessions_service.py python
156 lines 5.3 KB
7923a405 test(supercharge): comprehensive test suite overhaul — all 11 points Gabriel Cardona <gabriel@tellurstori.com> 6d ago
1 """Unit tests for musehub/services/musehub_sessions.py.
2
3 Tests the service layer directly (no HTTP) to validate session creation,
4 listing, retrieval, and ordering semantics.
5 """
6 from __future__ import annotations
7
8 from datetime import datetime, timedelta, timezone
9
10 import pytest
11 from sqlalchemy.ext.asyncio import AsyncSession
12
13 from musehub.models.musehub import SessionCreate
14 from musehub.services import musehub_sessions
15 from tests.factories import create_repo
16
17
18 def _session_create(**kwargs: object) -> SessionCreate:
19 defaults = dict(
20 participants=["alice"],
21 intent="Recording session",
22 location="Studio A",
23 )
24 defaults.update(kwargs)
25 return SessionCreate(**defaults)
26
27
28 @pytest.mark.anyio
29 async def test_upsert_session_creates_record(db_session: AsyncSession) -> None:
30 """upsert_session returns a SessionResponse with the expected fields."""
31 repo = await create_repo(db_session, visibility="public")
32 data = _session_create(participants=["bob", "alice"])
33
34 result = await musehub_sessions.upsert_session(db_session, str(repo.repo_id), data)
35
36 assert result.session_id is not None
37 assert set(result.participants) == {"bob", "alice"}
38 assert result.intent == "Recording session"
39 assert result.location == "Studio A"
40 assert result.is_active is True
41
42
43 @pytest.mark.anyio
44 async def test_upsert_session_uses_provided_started_at(db_session: AsyncSession) -> None:
45 """started_at from the request is preserved in the session record."""
46 repo = await create_repo(db_session)
47 t = datetime(2026, 1, 15, 10, 0, 0, tzinfo=timezone.utc)
48 data = _session_create(started_at=t)
49
50 result = await musehub_sessions.upsert_session(db_session, str(repo.repo_id), data)
51 await db_session.commit()
52
53 assert result.started_at is not None
54
55
56 @pytest.mark.anyio
57 async def test_list_sessions_empty_for_new_repo(db_session: AsyncSession) -> None:
58 """A new repo with no sessions returns an empty list and total=0."""
59 repo = await create_repo(db_session)
60 sessions, total = await musehub_sessions.list_sessions(db_session, str(repo.repo_id))
61 assert sessions == []
62 assert total == 0
63
64
65 @pytest.mark.anyio
66 async def test_list_sessions_returns_all_sessions(db_session: AsyncSession) -> None:
67 """list_sessions returns all sessions belonging to a repo."""
68 repo = await create_repo(db_session)
69 rid = str(repo.repo_id)
70
71 for i in range(3):
72 await musehub_sessions.upsert_session(
73 db_session, rid, _session_create(intent=f"Session {i}")
74 )
75 await db_session.commit()
76
77 sessions, total = await musehub_sessions.list_sessions(db_session, rid)
78 assert total == 3
79 assert len(sessions) == 3
80
81
82 @pytest.mark.anyio
83 async def test_list_sessions_ordered_newest_first(db_session: AsyncSession) -> None:
84 """list_sessions returns sessions sorted by started_at descending."""
85 repo = await create_repo(db_session)
86 rid = str(repo.repo_id)
87 now = datetime.now(tz=timezone.utc)
88
89 for i in range(3):
90 t = now - timedelta(hours=i)
91 await musehub_sessions.upsert_session(
92 db_session, rid, _session_create(started_at=t, intent=f"Session {i}")
93 )
94 await db_session.commit()
95
96 sessions, _ = await musehub_sessions.list_sessions(db_session, rid)
97 timestamps = [s.started_at for s in sessions]
98 assert timestamps == sorted(timestamps, reverse=True)
99
100
101 @pytest.mark.anyio
102 async def test_list_sessions_isolates_by_repo(db_session: AsyncSession) -> None:
103 """Sessions from one repo do not appear when listing another repo's sessions."""
104 repo_a = await create_repo(db_session, slug="repo-a-sess")
105 repo_b = await create_repo(db_session, slug="repo-b-sess")
106
107 await musehub_sessions.upsert_session(
108 db_session, str(repo_a.repo_id), _session_create()
109 )
110 await db_session.commit()
111
112 sessions, total = await musehub_sessions.list_sessions(db_session, str(repo_b.repo_id))
113 assert total == 0
114 assert sessions == []
115
116
117 @pytest.mark.anyio
118 async def test_get_session_by_id(db_session: AsyncSession) -> None:
119 """get_session returns the correct session given its ID."""
120 repo = await create_repo(db_session)
121 rid = str(repo.repo_id)
122
123 created = await musehub_sessions.upsert_session(
124 db_session, rid, _session_create(intent="Find me")
125 )
126 await db_session.commit()
127
128 found = await musehub_sessions.get_session(db_session, rid, created.session_id)
129 assert found is not None
130 assert found.session_id == created.session_id
131 assert found.intent == "Find me"
132
133
134 @pytest.mark.anyio
135 async def test_get_session_nonexistent_returns_none(db_session: AsyncSession) -> None:
136 """get_session returns None for an unknown session ID."""
137 repo = await create_repo(db_session)
138 result = await musehub_sessions.get_session(
139 db_session, str(repo.repo_id), "00000000-0000-0000-0000-000000000000"
140 )
141 assert result is None
142
143
144 @pytest.mark.anyio
145 async def test_list_sessions_limit_respected(db_session: AsyncSession) -> None:
146 """list_sessions respects the limit parameter."""
147 repo = await create_repo(db_session)
148 rid = str(repo.repo_id)
149
150 for i in range(5):
151 await musehub_sessions.upsert_session(db_session, rid, _session_create())
152 await db_session.commit()
153
154 sessions, total = await musehub_sessions.list_sessions(db_session, rid, limit=2)
155 assert total == 5
156 assert len(sessions) == 2