Files
notes-qpid-pro/tests/test_api.py
T

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