176 lines
5.4 KiB
Python
176 lines
5.4 KiB
Python
import os
|
|
import tempfile
|
|
import pytest
|
|
from pathlib import Path
|
|
from fastapi.testclient import TestClient
|
|
|
|
# Use temporary database for tests
|
|
temp_db = tempfile.NamedTemporaryFile(suffix=".db", delete=False)
|
|
os.environ["DB_PATH"] = temp_db.name
|
|
os.environ["ADMIN_USERNAME"] = "testadmin"
|
|
os.environ["ADMIN_PASSWORD"] = "testpass12345"
|
|
|
|
from app.main import app
|
|
from app import config
|
|
from app.database import init_db, get_db
|
|
from app.auth import hash_password, verify_password, create_user, authenticate_user, ensure_admin_user
|
|
|
|
config.ADMIN_USERNAME = "testadmin"
|
|
config.ADMIN_PASSWORD = "testpass12345"
|
|
|
|
client = TestClient(app)
|
|
|
|
def setup_module():
|
|
init_db()
|
|
ensure_admin_user()
|
|
|
|
def teardown_module():
|
|
try:
|
|
Path(temp_db.name).unlink(missing_ok=True)
|
|
except Exception:
|
|
pass
|
|
|
|
def test_password_hashing():
|
|
pwd = "superSecretPassword123"
|
|
h, salt = hash_password(pwd)
|
|
assert verify_password(pwd, h, salt) is True
|
|
assert verify_password("wrongPassword", h, salt) is False
|
|
|
|
def test_user_creation_and_auth():
|
|
uid = create_user("demo_user", "demo_pass_999")
|
|
assert uid > 0
|
|
auth = authenticate_user("demo_user", "demo_pass_999")
|
|
assert auth is not None
|
|
assert auth["username"] == "demo_user"
|
|
|
|
wrong = authenticate_user("demo_user", "wrong_pass")
|
|
assert wrong is None
|
|
|
|
def test_unauthenticated_access_blocked():
|
|
# Attempting to access protected API without session
|
|
resp = client.get("/api/me")
|
|
assert resp.status_code == 401
|
|
|
|
resp = client.get("/api/cards")
|
|
assert resp.status_code == 401
|
|
|
|
resp = client.post("/api/cards", json={"term": "apple", "translation": "яблоко"})
|
|
assert resp.status_code == 401
|
|
|
|
def test_login_and_card_crud_workflow():
|
|
# 1. Login with testadmin
|
|
resp = client.post("/api/login", json={"username": "testadmin", "password": "testpass12345"})
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert data["status"] == "ok"
|
|
assert "user" in data
|
|
|
|
# 2. Check /api/me
|
|
resp = client.get("/api/me")
|
|
assert resp.status_code == 200
|
|
assert resp.json()["user"]["username"] == "testadmin"
|
|
|
|
# 3. Create a card
|
|
card_payload = {
|
|
"term": "reluctant",
|
|
"translation": "неохотный, нежелающий",
|
|
"context_sentence": "He was reluctant to leave the room.",
|
|
"phonetic": "/rɪˈlʌktənt/",
|
|
"part_of_speech": "Adjective",
|
|
"definition_en": "Not wanting to take some action",
|
|
"tags": "general, daily",
|
|
"notes": "Remember: reluctant to do sth"
|
|
}
|
|
resp = client.post("/api/cards", json=card_payload)
|
|
assert resp.status_code == 200
|
|
created = resp.json()
|
|
card_id = created["id"]
|
|
assert created["term"] == "reluctant"
|
|
|
|
# 4. Get cards list
|
|
resp = client.get("/api/cards")
|
|
assert resp.status_code == 200
|
|
res_data = resp.json()
|
|
assert res_data["total"] >= 1
|
|
assert any(c["id"] == card_id for c in res_data["cards"])
|
|
|
|
# 5. Filter cards by search
|
|
resp = client.get("/api/cards?search=reluctant")
|
|
assert resp.status_code == 200
|
|
assert resp.json()["total"] >= 1
|
|
|
|
# 6. Filter cards by tag
|
|
resp = client.get("/api/cards?tag=general")
|
|
assert resp.status_code == 200
|
|
assert resp.json()["total"] >= 1
|
|
|
|
# 7. Check tags list
|
|
resp = client.get("/api/tags")
|
|
assert resp.status_code == 200
|
|
tags = resp.json()
|
|
tag_names = [t["tag"] for t in tags]
|
|
assert "general" in tag_names
|
|
assert "daily" in tag_names
|
|
|
|
# 8. Update card
|
|
update_payload = {
|
|
"translation": "неохотный, сопротивляющийся",
|
|
"notes": "Updated note"
|
|
}
|
|
resp = client.put(f"/api/cards/{card_id}", json=update_payload)
|
|
assert resp.status_code == 200
|
|
updated = resp.json()
|
|
assert updated["translation"] == "неохотный, сопротивляющийся"
|
|
assert updated["notes"] == "Updated note"
|
|
|
|
# 9. Check stats
|
|
resp = client.get("/api/stats")
|
|
assert resp.status_code == 200
|
|
stats = resp.json()
|
|
assert stats["total_cards"] >= 1
|
|
assert stats["today_added"] >= 1
|
|
|
|
# 10. Delete card
|
|
resp = client.delete(f"/api/cards/{card_id}")
|
|
assert resp.status_code == 200
|
|
assert resp.json()["status"] == "ok"
|
|
|
|
# Verify deleted
|
|
resp = client.get("/api/cards")
|
|
assert not any(c["id"] == card_id for c in resp.json()["cards"])
|
|
|
|
# 11. Logout
|
|
resp = client.post("/api/logout")
|
|
assert resp.status_code == 200
|
|
|
|
# Verify session ended
|
|
resp = client.get("/api/me")
|
|
assert resp.status_code == 401
|
|
|
|
def test_login_rate_limiting():
|
|
# Attempt multiple failed logins from the same client
|
|
for _ in range(5):
|
|
resp = client.post("/api/login", json={"username": "testadmin", "password": "wrong_password_999"})
|
|
assert resp.status_code == 401
|
|
|
|
# 6th attempt should be rate limited (429)
|
|
resp = client.post("/api/login", json={"username": "testadmin", "password": "wrong_password_999"})
|
|
assert resp.status_code == 429
|
|
|
|
def test_lookup_endpoint_authenticated():
|
|
from app.auth import _login_failures
|
|
_login_failures.clear()
|
|
|
|
# Login first
|
|
resp = client.post("/api/login", json={"username": "testadmin", "password": "testpass12345"})
|
|
assert resp.status_code == 200
|
|
|
|
resp = client.get("/api/lookup?q=reluctant")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert data["query"] == "reluctant"
|
|
assert "translation" in data
|
|
assert "phonetic" in data
|
|
assert len(data["translation"]) > 0
|
|
|