59 lines
1.8 KiB
Python
59 lines
1.8 KiB
Python
"""Tests for login attempt auditing (SEC-009)."""
|
|
|
|
from app.models.audit import AuditLog
|
|
|
|
|
|
def test_login_failed_creates_audit_entry(client, admin_user, db):
|
|
response = client.post(
|
|
"/api/v1/auth/login",
|
|
data={"username": "admin", "password": "wrong"},
|
|
headers={"X-Forwarded-For": "198.51.100.10", "User-Agent": "LoginAuditTest/1.0"},
|
|
)
|
|
assert response.status_code == 400
|
|
|
|
log = (
|
|
db.query(AuditLog)
|
|
.filter(AuditLog.action == "LOGIN_FAILED")
|
|
.order_by(AuditLog.timestamp.desc())
|
|
.first()
|
|
)
|
|
assert log is not None
|
|
assert log.entity_type == "auth"
|
|
assert log.details["username"] == "admin"
|
|
assert log.details["reason"] == "invalid_credentials"
|
|
assert log.ip_address == "198.51.100.10"
|
|
assert log.user_agent == "LoginAuditTest/1.0"
|
|
assert log.integrity_hash
|
|
|
|
|
|
def test_login_success_creates_audit_entry(client, admin_user, db):
|
|
client.cookies.clear()
|
|
response = client.post(
|
|
"/api/v1/auth/login",
|
|
data={"username": "admin", "password": "admin123"},
|
|
headers={"X-Forwarded-For": "198.51.100.20"},
|
|
)
|
|
assert response.status_code == 200
|
|
|
|
log = (
|
|
db.query(AuditLog)
|
|
.filter(AuditLog.action == "LOGIN_SUCCESS")
|
|
.order_by(AuditLog.timestamp.desc())
|
|
.first()
|
|
)
|
|
assert log is not None
|
|
assert log.user_id == admin_user.id
|
|
assert log.ip_address == "198.51.100.20"
|
|
assert log.integrity_hash
|
|
|
|
|
|
def test_login_unknown_user_still_audited(client, db):
|
|
response = client.post(
|
|
"/api/v1/auth/login",
|
|
data={"username": "nobody", "password": "password"},
|
|
)
|
|
assert response.status_code == 400
|
|
log = db.query(AuditLog).filter(AuditLog.action == "LOGIN_FAILED").first()
|
|
assert log is not None
|
|
assert log.user_id is None
|