Files
gh-christianlouis-docuelevate/tests/test_devices_page.py
copilot-swe-agent[bot] e518bce922 fix: merge main branch and renumber migration 037→040
Resolve all merge conflicts between our automation feature branch and
current main (v0.163.0, 920 commits ahead).

Conflicts resolved:
- app/api/__init__.py: add automation_router alongside main's new routers
  (classification_rules, qr_auth, sessions, system_reset)
- app/config.py: add main's new settings (dropbox_use_global_credentials,
  factory_reset_on_startup, enable_factory_reset)
- app/models.py: add main's new models (ClassificationRuleModel, UserSession,
  QRLoginChallenge, SharePoint integration type)
- app/utils/settings_service.py: merge automation_hooks_enabled with main's
  new metadata entries
- docs/API.md: merge automation API docs with main's classification rules docs
- docs/ConfigurationGuide.md: add factory reset settings
- tests/conftest.py: import both AutomationHook and new main models

Migration renumbered:
- 037_add_automation_hooks → 040_add_automation_hooks
- down_revision: 039_add_classification_rules (was 036_add_document_translation_fields)
- Chain: 036 → 037 → 038 → 039 → 040 (automation hooks)

For all non-automation files with conflicts, main's version was taken since
our branch did not modify those files (conflicts were from a stale prior merge).

Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
Agent-Logs-Url: https://github.com/christianlouis/DocuElevate/sessions/cb62f012-3b69-4415-835e-3857ce3e9f45
2026-03-20 23:54:04 +00:00

202 lines
6.8 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Tests for the Devices page and mobile token filtering (app/api/api_tokens.py mobile endpoint).
These tests validate:
- ``GET /api/api-tokens/mobile`` returns only mobile tokens
- ``GET /api/api-tokens/`` excludes mobile tokens
- ``GET /devices`` renders the devices page
"""
import pytest
from fastapi.testclient import TestClient
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.pool import StaticPool
from app.database import Base, get_db
from app.models import ApiToken
# ---------------------------------------------------------------------------
# Test data
# ---------------------------------------------------------------------------
_OWNER = "devices_user@example.com"
_OTHER_OWNER = "other_devices@example.com"
# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------
@pytest.fixture()
def dev_engine():
"""In-memory SQLite engine."""
engine = create_engine(
"sqlite:///:memory:",
connect_args={"check_same_thread": False},
poolclass=StaticPool,
)
Base.metadata.create_all(bind=engine)
yield engine
Base.metadata.drop_all(bind=engine)
@pytest.fixture()
def dev_session(dev_engine):
"""DB session scoped to one test."""
Session = sessionmaker(bind=dev_engine)
session = Session()
yield session
session.close()
def _make_client(dev_engine, owner_id: str = _OWNER) -> TestClient:
"""Return a TestClient with *owner_id* injected as the authenticated user."""
from app.api.api_tokens import _get_owner_id
from app.main import app
Session = sessionmaker(bind=dev_engine)
def _override_get_db():
session = Session()
try:
yield session
finally:
session.close()
def _override_owner():
return owner_id
app.dependency_overrides[get_db] = _override_get_db
app.dependency_overrides[_get_owner_id] = _override_owner
client = TestClient(app, base_url="http://localhost", raise_server_exceptions=False)
return client
def _cleanup(app):
"""Remove dependency overrides after test."""
app.dependency_overrides.clear()
def _seed_tokens(session, owner_id: str = _OWNER):
"""Create a mix of regular and mobile tokens for testing."""
from app.api.api_tokens import generate_api_token, hash_token
tokens = []
# Regular API tokens
for name in ["CI Pipeline", "Webhook Upload"]:
pt = generate_api_token()
t = ApiToken(owner_id=owner_id, name=name, token_hash=hash_token(pt), token_prefix=pt[:12])
session.add(t)
tokens.append(t)
# Mobile tokens (various naming patterns)
for name in [
"Mobile App iPhone 15 Pro",
"Mobile App (QR) Christian's iPad",
"Mobile App",
]:
pt = generate_api_token()
t = ApiToken(owner_id=owner_id, name=name, token_hash=hash_token(pt), token_prefix=pt[:12])
session.add(t)
tokens.append(t)
session.commit()
return tokens
# ---------------------------------------------------------------------------
# Tests Mobile Token Filtering
# ---------------------------------------------------------------------------
@pytest.mark.unit
class TestMobileTokenFiltering:
"""Tests for GET /api/api-tokens/mobile and filtering from GET /api/api-tokens/."""
def test_list_mobile_tokens_returns_only_mobile(self, dev_engine, dev_session):
"""GET /api/api-tokens/mobile should only return tokens starting with 'Mobile App'."""
_seed_tokens(dev_session)
client = _make_client(dev_engine)
try:
res = client.get("/api/api-tokens/mobile")
assert res.status_code == 200
data = res.json()
assert len(data) == 3
for t in data:
assert t["name"].startswith("Mobile App")
finally:
_cleanup(client.app)
def test_list_regular_tokens_excludes_mobile(self, dev_engine, dev_session):
"""GET /api/api-tokens/ should NOT return tokens starting with 'Mobile App'."""
_seed_tokens(dev_session)
client = _make_client(dev_engine)
try:
res = client.get("/api/api-tokens/")
assert res.status_code == 200
data = res.json()
assert len(data) == 2
for t in data:
assert not t["name"].startswith("Mobile App")
finally:
_cleanup(client.app)
def test_list_mobile_tokens_empty(self, dev_engine):
"""GET /api/api-tokens/mobile returns [] when no mobile tokens exist."""
client = _make_client(dev_engine)
try:
res = client.get("/api/api-tokens/mobile")
assert res.status_code == 200
assert res.json() == []
finally:
_cleanup(client.app)
def test_list_mobile_tokens_isolation(self, dev_engine, dev_session):
"""Mobile tokens for other users should not appear."""
_seed_tokens(dev_session, owner_id=_OTHER_OWNER)
client = _make_client(dev_engine, owner_id=_OWNER)
try:
res = client.get("/api/api-tokens/mobile")
assert res.status_code == 200
assert res.json() == []
finally:
_cleanup(client.app)
def test_mobile_token_revoke_via_api_tokens_endpoint(self, dev_engine, dev_session):
"""Mobile tokens can still be revoked via DELETE /api/api-tokens/{id}."""
tokens = _seed_tokens(dev_session)
mobile_token = next(t for t in tokens if t.name.startswith("Mobile App"))
client = _make_client(dev_engine)
try:
res = client.delete(f"/api/api-tokens/{mobile_token.id}")
assert res.status_code == 200
# Verify it's gone from mobile list
res2 = client.get("/api/api-tokens/mobile")
active_names = [t["name"] for t in res2.json() if t["is_active"]]
assert mobile_token.name not in active_names
finally:
_cleanup(client.app)
# ---------------------------------------------------------------------------
# Tests Devices Page View
# ---------------------------------------------------------------------------
@pytest.mark.unit
class TestDevicesPageView:
"""Tests for GET /devices page rendering."""
def test_devices_page_renders(self, dev_engine):
"""GET /devices should return 200 with the devices template."""
from app.views.devices import router as _ # noqa: F401 ensures route is registered
client = _make_client(dev_engine)
try:
res = client.get("/devices")
assert res.status_code == 200
assert "devices.heading" in res.text or "Mobile Devices" in res.text
finally:
_cleanup(client.app)