from datetime import datetime, timedelta, timezone from app.models.saas import AuthSession from app.services.identity import IdentityRepository, PasswordService, SessionService def test_session_token_is_hashed_and_resolvable(): from sqlalchemy import create_engine from sqlalchemy.orm import Session from app.db import Base engine = create_engine("sqlite+pysqlite:///:memory:") Base.metadata.create_all(engine) session = Session(engine) try: repo = IdentityRepository(session) org = repo.create_organization(name="Org A", slug="org-a") user = repo.create_user( email="admin@example.com", password_hash=PasswordService.hash_password("correct horse battery staple"), ) membership = repo.create_membership(user.id, org.id, "admin") raw_token, stored = SessionService.create(session, user, membership.id) session.commit() assert raw_token != stored.token_hash assert len(stored.token_hash) == 64 resolved = SessionService.resolve(session, raw_token) assert resolved is not None assert resolved.user.id == user.id assert resolved.membership.id == membership.id assert resolved.organization.id == org.id finally: session.close() engine.dispose() def test_revoked_or_stale_auth_version_session_is_not_resolvable(): from sqlalchemy import create_engine from sqlalchemy.orm import Session from app.db import Base engine = create_engine("sqlite+pysqlite:///:memory:") Base.metadata.create_all(engine) session = Session(engine) try: repo = IdentityRepository(session) org = repo.create_organization(name="Org A", slug="org-a") user = repo.create_user(email="user@example.com") membership = repo.create_membership(user.id, org.id, "user") raw_token, _stored = SessionService.create(session, user, membership.id) session.commit() user.auth_version += 1 session.commit() assert SessionService.resolve(session, raw_token) is None user.auth_version -= 1 session.commit() assert SessionService.revoke(session, raw_token) is True session.commit() assert SessionService.resolve(session, raw_token) is None finally: session.close() engine.dispose() def test_expired_session_is_not_resolvable(): from sqlalchemy import create_engine from sqlalchemy.orm import Session from app.db import Base engine = create_engine("sqlite+pysqlite:///:memory:") Base.metadata.create_all(engine) session = Session(engine) try: repo = IdentityRepository(session) org = repo.create_organization(name="Org A", slug="org-a") user = repo.create_user(email="expired@example.com") membership = repo.create_membership(user.id, org.id, "user") raw_token, stored = SessionService.create(session, user, membership.id) stored.expires_at = datetime.now(timezone.utc) - timedelta(seconds=1) session.commit() assert SessionService.resolve(session, raw_token) is None finally: session.close() engine.dispose()