Files
launcher/server/tests/test_client_contract.py
T
SashegDev c0310ed573 test(server): add comprehensive test suite (47 tests), fix DB lock and schema bugs
- Add pytest test suite: test_auth.py, test_admin.py, test_pass.py,
  test_proxy.py, test_rate_limit.py, test_client_contract.py
- Fix SQLite 'database is locked' errors: moved log_audit() calls outside
  with get_db() blocks in register, login, logout, refresh, activate_pass
- Enable WAL mode and busy_timeout in get_db() for concurrent access
- Fix /admin/me: removed non-existent 'email' column from query
- Fix /admin/users list: disambiguated activated_at column in JOIN query
- Fix /auth/refresh: now returns refresh_token + expires_in + username/uuid/role
  to match AuthManager.AuthSession expectations; revokes old refresh token
- Fix conftest.py: unique usernames per test to avoid conflicts
- All 47 tests passing
2026-05-04 22:14:06 +00:00

143 lines
5.5 KiB
Python

"""Tests verifying server responses match client (AuthManager.java) expectations."""
import pytest
from tests.conftest import auth_headers
class TestAuthResponseContract:
"""Verify /auth/register and /auth/login response fields match AuthSession.java."""
def test_register_has_all_session_fields(self, client):
"""Client expects: access_token, refresh_token, expires_in, uuid, username, role."""
resp = client.post("/auth/register", json={
"username": "contracttest",
"password": "ContractPass123"
})
assert resp.status_code == 200
data = resp.json()
# AuthManager.AuthSession fields
assert "access_token" in data, "Client needs access_token"
assert "refresh_token" in data, "Client needs refresh_token"
assert "expires_in" in data, "Client needs expires_in (int)"
assert "uuid" in data, "Client needs uuid"
assert "username" in data, "Client needs username"
assert "role" in data, "Client needs role (int)"
# Type checks
assert isinstance(data["access_token"], str)
assert isinstance(data["refresh_token"], str)
assert isinstance(data["expires_in"], int)
assert isinstance(data["uuid"], str)
assert isinstance(data["role"], int)
assert data["expires_in"] > 0 # Must be positive seconds
def test_login_has_all_session_fields(self, client, registered_user):
resp = client.post("/auth/login", json=registered_user)
assert resp.status_code == 200
data = resp.json()
assert "access_token" in data
assert "refresh_token" in data
assert "expires_in" in data
assert "uuid" in data
assert "username" in data
assert "role" in data
assert isinstance(data["expires_in"], int)
assert isinstance(data["role"], int)
class TestValidateResponseContract:
"""Verify /auth/validate response matches client expectations."""
def test_validate_valid_response_fields(self, client, logged_in_user):
"""Client checks: valid (bool), username, uuid, role."""
resp = client.post("/auth/validate", json={
"username": logged_in_user["username"],
"uuid": logged_in_user["uuid"]
}, headers=auth_headers(logged_in_user["access_token"]))
assert resp.status_code == 200
data = resp.json()
assert "valid" in data
assert isinstance(data["valid"], bool)
assert data["valid"] is True
assert "username" in data
assert "uuid" in data
def test_validate_invalid_response_fields(self, client):
resp = client.post("/auth/validate", json={
"username": "test",
"uuid": "test"
}, headers=auth_headers("bad.token"))
assert resp.status_code == 401 # Invalid token returns 401
class TestAdminMeResponseContract:
"""Verify /admin/me response matches UserInfo.java expectations."""
def test_admin_me_has_all_userinfo_fields(self, client, logged_in_user):
"""
Client UserInfo.java expects:
id (int), username, uuid, role (int), role_name, has_pass (bool), permissions (list)
"""
resp = client.get("/admin/me", headers=auth_headers(logged_in_user["access_token"]))
assert resp.status_code == 200
data = resp.json()
assert "id" in data, "UserInfo needs id"
assert "username" in data
assert "uuid" in data
assert "role" in data, "UserInfo needs role"
assert "role_name" in data, "UserInfo needs role_name"
assert "has_pass" in data, "UserInfo needs has_pass"
assert "permissions" in data, "UserInfo needs permissions"
# Type checks
assert isinstance(data["id"], int)
assert isinstance(data["role"], int)
assert isinstance(data["has_pass"], bool)
assert isinstance(data["permissions"], list)
assert isinstance(data["role_name"], str)
class TestErrorResponseContract:
"""Verify error responses match client extractError() parsing."""
def test_error_has_detail_field(self, client):
"""Client parses json.detail (string or array with msg)."""
resp = client.post("/auth/login", json={
"username": "nonexistent",
"password": "wrong"
})
# FastAPI returns 422 for validation errors, auth errors return 401
assert resp.status_code in (401, 422)
data = resp.json()
assert "detail" in data, "Client expects 'detail' field in errors"
assert isinstance(data["detail"], (str, list))
def test_validation_error_has_detail_array(self, client):
"""FastAPI 422 returns detail as array of {loc, msg, type}."""
resp = client.post("/auth/register", json={
"username": "ab",
"password": "x"
})
assert resp.status_code == 422
data = resp.json()
assert "detail" in data
assert isinstance(data["detail"], list)
assert "msg" in data["detail"][0]
class TestPackResponseContract:
"""Verify /packs response matches client expectations."""
def test_packs_response_structure(self, client, logged_in_user):
resp = client.get("/packs", headers=auth_headers(logged_in_user["access_token"]))
# May return 200 or 401/403 depending on auth setup
assert resp.status_code in (200, 401, 403)
if resp.status_code == 200:
data = resp.json()
assert "packs" in data
assert isinstance(data["packs"], list)