Files
security-alert-center/backend/tests/test_mobile_api.py
T
PapaTramp 3d36faa49d fix(mobile): повторный enroll и удаление устройств в UI (0.9.10).
При повторной привязке Seaca переиспользуется запись device_uuid вместо INSERT.
Добавлены POST /devices/{id}/revoke и DELETE для полного удаления записи в настройках SAC.
2026-06-13 16:36:07 +10:00

228 lines
7.3 KiB
Python

"""Mobile / Seaca API."""
from datetime import datetime, timedelta, timezone
from app.models.mobile_device import MobileDevice
from app.models.mobile_enrollment_code import MobileEnrollmentCode
from app.models.mobile_settings import MOBILE_SETTINGS_ROW_ID, MobileSettings
from app.services.mobile_tokens import hash_mobile_secret
def _enable_mobile(db_session):
row = db_session.get(MobileSettings, MOBILE_SETTINGS_ROW_ID)
if row is None:
row = MobileSettings(id=MOBILE_SETTINGS_ROW_ID, devices_allowed=True, max_devices_per_user=3)
db_session.add(row)
else:
row.devices_allowed = True
db_session.commit()
def test_mobile_settings_admin(jwt_headers, client, db_session):
_enable_mobile(db_session)
response = client.get("/api/v1/mobile/settings", headers=jwt_headers)
assert response.status_code == 200
body = response.json()
assert body["devices_allowed"] is True
assert body["max_devices_per_user"] == 3
def test_create_enrollment_code_and_enroll(jwt_headers, client, db_session):
_enable_mobile(db_session)
create = client.post(
"/api/v1/mobile/enrollment-codes",
headers=jwt_headers,
json={"label": "test phone", "login_mode": "password", "expires_in_hours": 1},
)
assert create.status_code == 200
created = create.json()
assert created["enrollment_code"].startswith("sacmob_")
assert created["code_prefix"]
enroll = client.post(
"/api/v1/mobile/enroll",
json={
"enrollment_code": created["enrollment_code"],
"username": "test-monitor",
"password": "test-monitor-password",
"device_uuid": "uuid-test-device-001",
"display_name": "Pixel Test",
"fcm_token": "fcm-token-test",
},
)
assert enroll.status_code == 200
body = enroll.json()
assert body["username"] == "test-monitor"
assert body["role"] == "monitor"
assert body["device_id"] > 0
assert body["access_token"]
assert body["refresh_token"]
devices = client.get("/api/v1/mobile/devices", headers=jwt_headers)
assert devices.status_code == 200
assert len(devices.json()) == 1
assert devices.json()[0]["display_name"] == "Pixel Test"
def test_enroll_blocked_when_disabled(client, db_session, jwt_headers):
code_plain = "sacmob_testcode123456"
db_session.add(
MobileEnrollmentCode(
label="x",
code_hash=hash_mobile_secret(code_plain),
code_prefix=code_plain[:12],
login_mode="password",
max_uses=1,
use_count=0,
expires_at=datetime.now(timezone.utc) + timedelta(hours=1),
)
)
db_session.commit()
response = client.post(
"/api/v1/mobile/enroll",
json={
"enrollment_code": code_plain,
"username": "test-monitor",
"password": "test-monitor-password",
"device_uuid": "uuid-disabled-001",
},
)
assert response.status_code == 403
def test_refresh_token_rotation(client, db_session, jwt_headers):
_enable_mobile(db_session)
create = client.post(
"/api/v1/mobile/enrollment-codes",
headers=jwt_headers,
json={"login_mode": "password"},
)
code = create.json()["enrollment_code"]
enroll = client.post(
"/api/v1/mobile/enroll",
json={
"enrollment_code": code,
"username": "test-admin",
"password": "test-admin-password",
"device_uuid": "uuid-refresh-001",
},
)
data = enroll.json()
refresh = client.post(
"/api/v1/mobile/auth/refresh",
json={"refresh_token": data["refresh_token"], "device_uuid": "uuid-refresh-001"},
)
assert refresh.status_code == 200
refreshed = refresh.json()
assert refreshed["refresh_token"] != data["refresh_token"]
assert refreshed["device_id"] == data["device_id"]
def test_revoke_device_blocks_token(client, db_session, jwt_headers):
_enable_mobile(db_session)
create = client.post(
"/api/v1/mobile/enrollment-codes",
headers=jwt_headers,
json={"login_mode": "password"},
)
code = create.json()["enrollment_code"]
enroll = client.post(
"/api/v1/mobile/enroll",
json={
"enrollment_code": code,
"username": "test-monitor",
"password": "test-monitor-password",
"device_uuid": "uuid-revoke-001",
},
)
data = enroll.json()
headers = {"Authorization": f"Bearer {data['access_token']}"}
revoke = client.post(f"/api/v1/mobile/devices/{data['device_id']}/revoke", headers=jwt_headers)
assert revoke.status_code == 200
me = client.put(
"/api/v1/mobile/devices/me/fcm",
headers=headers,
json={"fcm_token": "new-token"},
)
assert me.status_code == 401
def test_reenroll_same_device_after_revoke(client, db_session, jwt_headers):
_enable_mobile(db_session)
device_uuid = "uuid-reenroll-001"
first_code = client.post(
"/api/v1/mobile/enrollment-codes",
headers=jwt_headers,
json={"login_mode": "password"},
).json()["enrollment_code"]
first = client.post(
"/api/v1/mobile/enroll",
json={
"enrollment_code": first_code,
"username": "test-monitor",
"password": "test-monitor-password",
"device_uuid": device_uuid,
"display_name": "Phone A",
},
)
assert first.status_code == 200
first_device_id = first.json()["device_id"]
revoke = client.post(f"/api/v1/mobile/devices/{first_device_id}/revoke", headers=jwt_headers)
assert revoke.status_code == 200
second_code = client.post(
"/api/v1/mobile/enrollment-codes",
headers=jwt_headers,
json={"login_mode": "password"},
).json()["enrollment_code"]
second = client.post(
"/api/v1/mobile/enroll",
json={
"enrollment_code": second_code,
"username": "test-monitor",
"password": "test-monitor-password",
"device_uuid": device_uuid,
"display_name": "Phone B",
},
)
assert second.status_code == 200
assert second.json()["device_id"] == first_device_id
devices = client.get("/api/v1/mobile/devices", headers=jwt_headers)
assert devices.status_code == 200
assert len(devices.json()) == 1
assert devices.json()[0]["display_name"] == "Phone B"
assert devices.json()[0]["is_active"] is True
def test_delete_device_record(client, db_session, jwt_headers):
_enable_mobile(db_session)
create = client.post(
"/api/v1/mobile/enrollment-codes",
headers=jwt_headers,
json={"login_mode": "password"},
)
code = create.json()["enrollment_code"]
enroll = client.post(
"/api/v1/mobile/enroll",
json={
"enrollment_code": code,
"username": "test-monitor",
"password": "test-monitor-password",
"device_uuid": "uuid-delete-001",
},
)
device_id = enroll.json()["device_id"]
deleted = client.delete(f"/api/v1/mobile/devices/{device_id}", headers=jwt_headers)
assert deleted.status_code == 204
devices = client.get("/api/v1/mobile/devices", headers=jwt_headers)
assert devices.status_code == 200
assert devices.json() == []