2026-02-24 14:36:31 +00:00
|
|
|
"""Test API routes"""
|
|
|
|
|
import pytest
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestAuthRoutes:
|
|
|
|
|
"""Test authentication routes"""
|
|
|
|
|
|
|
|
|
|
@pytest.mark.auth
|
|
|
|
|
def test_register_success(self, client):
|
|
|
|
|
"""Test successful user registration"""
|
2026-02-24 16:19:15 +00:00
|
|
|
response = client.post(
|
|
|
|
|
"/api/auth/register",
|
|
|
|
|
json={
|
|
|
|
|
"email": "newuser@example.com",
|
|
|
|
|
"password": "password123",
|
|
|
|
|
"username": "newuser",
|
|
|
|
|
"first_name": "New",
|
|
|
|
|
"last_name": "User",
|
|
|
|
|
},
|
|
|
|
|
)
|
2026-02-24 14:36:31 +00:00
|
|
|
|
|
|
|
|
assert response.status_code == 201
|
|
|
|
|
data = response.get_json()
|
2026-02-24 16:19:15 +00:00
|
|
|
assert data["email"] == "newuser@example.com"
|
|
|
|
|
assert data["username"] == "newuser"
|
|
|
|
|
assert "password" not in data
|
|
|
|
|
assert "password_hash" not in data
|
2026-02-24 14:36:31 +00:00
|
|
|
|
|
|
|
|
@pytest.mark.auth
|
|
|
|
|
def test_register_missing_fields(self, client):
|
|
|
|
|
"""Test registration with missing required fields"""
|
2026-02-24 16:19:15 +00:00
|
|
|
response = client.post(
|
|
|
|
|
"/api/auth/register", json={"email": "newuser@example.com"}
|
|
|
|
|
)
|
2026-02-24 14:36:31 +00:00
|
|
|
|
|
|
|
|
assert response.status_code == 400
|
|
|
|
|
data = response.get_json()
|
2026-02-24 16:19:15 +00:00
|
|
|
assert "error" in data
|
2026-02-24 14:36:31 +00:00
|
|
|
|
|
|
|
|
@pytest.mark.auth
|
|
|
|
|
def test_register_duplicate_email(self, client, regular_user):
|
|
|
|
|
"""Test registration with duplicate email"""
|
2026-02-24 16:19:15 +00:00
|
|
|
response = client.post(
|
|
|
|
|
"/api/auth/register",
|
|
|
|
|
json={"email": regular_user.email, "password": "password123"},
|
|
|
|
|
)
|
2026-02-24 14:36:31 +00:00
|
|
|
|
|
|
|
|
assert response.status_code == 400
|
|
|
|
|
data = response.get_json()
|
2026-02-24 16:19:15 +00:00
|
|
|
assert "already exists" in data["error"].lower()
|
2026-02-24 14:36:31 +00:00
|
|
|
|
|
|
|
|
@pytest.mark.auth
|
|
|
|
|
def test_login_success(self, client, regular_user):
|
|
|
|
|
"""Test successful login"""
|
2026-02-24 16:19:15 +00:00
|
|
|
response = client.post(
|
|
|
|
|
"/api/auth/login",
|
|
|
|
|
json={"email": regular_user.email, "password": "password123"},
|
|
|
|
|
)
|
2026-02-24 14:36:31 +00:00
|
|
|
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
data = response.get_json()
|
2026-02-24 16:19:15 +00:00
|
|
|
assert "access_token" in data
|
|
|
|
|
assert "refresh_token" in data
|
|
|
|
|
assert data["user"]["email"] == regular_user.email
|
2026-02-24 14:36:31 +00:00
|
|
|
|
|
|
|
|
@pytest.mark.auth
|
2026-02-24 16:19:15 +00:00
|
|
|
@pytest.mark.parametrize(
|
|
|
|
|
"email,password,expected_status",
|
|
|
|
|
[
|
|
|
|
|
("wrong@example.com", "password123", 401),
|
|
|
|
|
("user@example.com", "wrongpassword", 401),
|
|
|
|
|
(None, "password123", 400),
|
|
|
|
|
("user@example.com", None, 400),
|
|
|
|
|
],
|
|
|
|
|
)
|
|
|
|
|
def test_login_validation(
|
|
|
|
|
self, client, regular_user, email, password, expected_status
|
|
|
|
|
):
|
2026-02-24 14:36:31 +00:00
|
|
|
"""Test login with various invalid inputs"""
|
|
|
|
|
login_data = {}
|
|
|
|
|
if email is not None:
|
2026-02-24 16:19:15 +00:00
|
|
|
login_data["email"] = email
|
2026-02-24 14:36:31 +00:00
|
|
|
if password is not None:
|
2026-02-24 16:19:15 +00:00
|
|
|
login_data["password"] = password
|
2026-02-24 14:36:31 +00:00
|
|
|
|
2026-02-24 16:19:15 +00:00
|
|
|
response = client.post("/api/auth/login", json=login_data)
|
2026-02-24 14:36:31 +00:00
|
|
|
assert response.status_code == expected_status
|
|
|
|
|
|
|
|
|
|
@pytest.mark.auth
|
|
|
|
|
def test_login_inactive_user(self, client, inactive_user):
|
|
|
|
|
"""Test login with inactive user"""
|
2026-02-24 16:19:15 +00:00
|
|
|
response = client.post(
|
|
|
|
|
"/api/auth/login",
|
|
|
|
|
json={"email": inactive_user.email, "password": "password123"},
|
|
|
|
|
)
|
2026-02-24 14:36:31 +00:00
|
|
|
|
|
|
|
|
assert response.status_code == 401
|
|
|
|
|
data = response.get_json()
|
2026-02-24 16:19:15 +00:00
|
|
|
assert "inactive" in data["error"].lower()
|
2026-02-24 14:36:31 +00:00
|
|
|
|
|
|
|
|
@pytest.mark.auth
|
|
|
|
|
def test_get_current_user(self, client, auth_headers, regular_user):
|
|
|
|
|
"""Test getting current user"""
|
2026-02-24 16:19:15 +00:00
|
|
|
response = client.get("/api/users/me", headers=auth_headers)
|
2026-02-24 14:36:31 +00:00
|
|
|
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
data = response.get_json()
|
2026-02-24 16:19:15 +00:00
|
|
|
assert data["email"] == regular_user.email
|
2026-02-24 14:36:31 +00:00
|
|
|
|
|
|
|
|
@pytest.mark.auth
|
|
|
|
|
def test_get_current_user_unauthorized(self, client):
|
|
|
|
|
"""Test getting current user without authentication"""
|
2026-02-24 16:19:15 +00:00
|
|
|
response = client.get("/api/users/me")
|
2026-02-24 14:36:31 +00:00
|
|
|
assert response.status_code == 401
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestProductRoutes:
|
|
|
|
|
"""Test product routes"""
|
|
|
|
|
|
|
|
|
|
@pytest.mark.product
|
2026-02-25 18:32:57 +00:00
|
|
|
def test_get_products(self, app, client, products):
|
2026-02-24 14:36:31 +00:00
|
|
|
"""Test getting all products"""
|
2026-02-25 18:32:57 +00:00
|
|
|
from app.models import Product
|
|
|
|
|
|
|
|
|
|
before_count = Product.query.count()
|
2026-02-24 16:19:15 +00:00
|
|
|
response = client.get("/api/products")
|
2026-02-24 14:36:31 +00:00
|
|
|
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
data = response.get_json()
|
2026-02-25 18:32:57 +00:00
|
|
|
assert len(data) == before_count
|
2026-02-24 14:36:31 +00:00
|
|
|
|
|
|
|
|
@pytest.mark.product
|
|
|
|
|
def test_get_products_empty(self, client):
|
|
|
|
|
"""Test getting products when none exist"""
|
2026-02-25 18:32:57 +00:00
|
|
|
from app.models import Product
|
|
|
|
|
|
|
|
|
|
before_count = Product.query.count()
|
2026-02-24 16:19:15 +00:00
|
|
|
response = client.get("/api/products")
|
2026-02-24 14:36:31 +00:00
|
|
|
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
data = response.get_json()
|
2026-02-25 18:32:57 +00:00
|
|
|
assert len(data) == before_count
|
2026-02-24 14:36:31 +00:00
|
|
|
|
|
|
|
|
@pytest.mark.product
|
|
|
|
|
def test_get_single_product(self, client, product):
|
|
|
|
|
"""Test getting a single product"""
|
2026-02-24 16:19:15 +00:00
|
|
|
response = client.get(f"/api/products/{product.id}")
|
2026-02-24 14:36:31 +00:00
|
|
|
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
data = response.get_json()
|
2026-02-24 16:19:15 +00:00
|
|
|
assert data["id"] == product.id
|
|
|
|
|
assert data["name"] == product.name
|
2026-02-24 14:36:31 +00:00
|
|
|
|
|
|
|
|
@pytest.mark.product
|
|
|
|
|
def test_get_product_not_found(self, client):
|
|
|
|
|
"""Test getting non-existent product"""
|
2026-02-24 16:19:15 +00:00
|
|
|
response = client.get("/api/products/999")
|
2026-02-24 14:36:31 +00:00
|
|
|
assert response.status_code == 404
|
|
|
|
|
|
|
|
|
|
@pytest.mark.product
|
|
|
|
|
def test_create_product_admin(self, client, admin_headers):
|
|
|
|
|
"""Test creating product as admin"""
|
2026-02-24 16:19:15 +00:00
|
|
|
response = client.post(
|
|
|
|
|
"/api/products",
|
|
|
|
|
headers=admin_headers,
|
|
|
|
|
json={
|
|
|
|
|
"name": "New Product",
|
|
|
|
|
"description": "A new product",
|
|
|
|
|
"price": 29.99,
|
|
|
|
|
"stock": 10,
|
|
|
|
|
},
|
|
|
|
|
)
|
2026-02-24 14:36:31 +00:00
|
|
|
|
|
|
|
|
assert response.status_code == 201
|
|
|
|
|
data = response.get_json()
|
2026-02-24 16:19:15 +00:00
|
|
|
assert data["name"] == "New Product"
|
|
|
|
|
assert data["price"] == 29.99
|
2026-02-24 14:36:31 +00:00
|
|
|
|
|
|
|
|
@pytest.mark.product
|
|
|
|
|
def test_create_product_regular_user(self, client, auth_headers):
|
|
|
|
|
"""Test creating product as regular user (should fail)"""
|
2026-02-24 16:19:15 +00:00
|
|
|
response = client.post(
|
|
|
|
|
"/api/products",
|
|
|
|
|
headers=auth_headers,
|
|
|
|
|
json={"name": "New Product", "price": 29.99},
|
|
|
|
|
)
|
2026-02-24 14:36:31 +00:00
|
|
|
|
|
|
|
|
assert response.status_code == 403
|
|
|
|
|
data = response.get_json()
|
2026-02-24 16:19:15 +00:00
|
|
|
assert "admin" in data["error"].lower()
|
2026-02-24 14:36:31 +00:00
|
|
|
|
|
|
|
|
@pytest.mark.product
|
|
|
|
|
def test_create_product_unauthorized(self, client):
|
|
|
|
|
"""Test creating product without authentication"""
|
2026-02-24 16:19:15 +00:00
|
|
|
response = client.post(
|
|
|
|
|
"/api/products", json={"name": "New Product", "price": 29.99}
|
|
|
|
|
)
|
2026-02-24 14:36:31 +00:00
|
|
|
|
|
|
|
|
assert response.status_code == 401
|
|
|
|
|
|
|
|
|
|
@pytest.mark.product
|
|
|
|
|
def test_create_product_validation_error(self, client, admin_headers):
|
|
|
|
|
"""Test creating product with invalid data"""
|
2026-02-24 16:19:15 +00:00
|
|
|
response = client.post(
|
|
|
|
|
"/api/products",
|
|
|
|
|
headers=admin_headers,
|
|
|
|
|
json={"name": "New Product", "price": -10.99},
|
|
|
|
|
)
|
2026-02-24 14:36:31 +00:00
|
|
|
|
|
|
|
|
assert response.status_code == 400
|
|
|
|
|
data = response.get_json()
|
2026-02-24 16:19:15 +00:00
|
|
|
assert "Validation error" in data["error"]
|
2026-02-24 14:36:31 +00:00
|
|
|
|
|
|
|
|
@pytest.mark.product
|
|
|
|
|
def test_create_product_missing_required_fields(self, client, admin_headers):
|
|
|
|
|
"""Test creating product with missing required fields"""
|
2026-02-24 16:19:15 +00:00
|
|
|
response = client.post(
|
|
|
|
|
"/api/products",
|
|
|
|
|
headers=admin_headers,
|
|
|
|
|
json={"description": "Missing name and price"},
|
|
|
|
|
)
|
2026-02-24 14:36:31 +00:00
|
|
|
|
|
|
|
|
assert response.status_code == 400
|
|
|
|
|
data = response.get_json()
|
2026-02-24 16:19:15 +00:00
|
|
|
assert "Validation error" in data["error"]
|
2026-02-24 14:36:31 +00:00
|
|
|
|
|
|
|
|
@pytest.mark.product
|
|
|
|
|
def test_create_product_minimal_data(self, client, admin_headers):
|
|
|
|
|
"""Test creating product with minimal valid data"""
|
2026-02-24 16:19:15 +00:00
|
|
|
response = client.post(
|
|
|
|
|
"/api/products",
|
|
|
|
|
headers=admin_headers,
|
|
|
|
|
json={"name": "Minimal Product", "price": 19.99},
|
|
|
|
|
)
|
2026-02-24 14:36:31 +00:00
|
|
|
|
|
|
|
|
assert response.status_code == 201
|
|
|
|
|
data = response.get_json()
|
2026-02-24 16:19:15 +00:00
|
|
|
assert data["name"] == "Minimal Product"
|
|
|
|
|
assert data["stock"] == 0 # Default value
|
2026-02-24 14:36:31 +00:00
|
|
|
|
|
|
|
|
@pytest.mark.product
|
|
|
|
|
def test_update_product_admin(self, client, admin_headers, product):
|
|
|
|
|
"""Test updating product as admin"""
|
2026-02-24 16:19:15 +00:00
|
|
|
response = client.put(
|
|
|
|
|
f"/api/products/{product.id}",
|
|
|
|
|
headers=admin_headers,
|
|
|
|
|
json={"name": "Updated Product", "price": 39.99},
|
|
|
|
|
)
|
2026-02-24 14:36:31 +00:00
|
|
|
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
data = response.get_json()
|
2026-02-24 16:19:15 +00:00
|
|
|
assert data["name"] == "Updated Product"
|
|
|
|
|
assert data["price"] == 39.99
|
2026-02-24 14:36:31 +00:00
|
|
|
|
|
|
|
|
@pytest.mark.product
|
|
|
|
|
def test_delete_product_admin(self, client, admin_headers, product):
|
|
|
|
|
"""Test deleting product as admin"""
|
2026-02-24 16:19:15 +00:00
|
|
|
response = client.delete(f"/api/products/{product.id}", headers=admin_headers)
|
2026-02-24 14:36:31 +00:00
|
|
|
assert response.status_code == 200
|
|
|
|
|
|
|
|
|
|
# Verify product is deleted
|
2026-02-24 16:19:15 +00:00
|
|
|
response = client.get(f"/api/products/{product.id}")
|
2026-02-24 14:36:31 +00:00
|
|
|
assert response.status_code == 404
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestOrderRoutes:
|
|
|
|
|
"""Test order routes"""
|
|
|
|
|
|
|
|
|
|
@pytest.mark.order
|
|
|
|
|
def test_get_orders(self, client, auth_headers, order):
|
|
|
|
|
"""Test getting orders for current user"""
|
2026-02-24 16:19:15 +00:00
|
|
|
response = client.get("/api/orders", headers=auth_headers)
|
2026-02-24 14:36:31 +00:00
|
|
|
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
data = response.get_json()
|
|
|
|
|
assert len(data) >= 1
|
|
|
|
|
|
|
|
|
|
@pytest.mark.order
|
|
|
|
|
def test_get_orders_unauthorized(self, client):
|
|
|
|
|
"""Test getting orders without authentication"""
|
2026-02-24 16:19:15 +00:00
|
|
|
response = client.get("/api/orders")
|
2026-02-24 14:36:31 +00:00
|
|
|
assert response.status_code == 401
|
|
|
|
|
|
|
|
|
|
@pytest.mark.order
|
|
|
|
|
def test_create_order(self, client, auth_headers, products):
|
|
|
|
|
"""Test creating an order"""
|
2026-02-24 16:19:15 +00:00
|
|
|
response = client.post(
|
|
|
|
|
"/api/orders",
|
|
|
|
|
headers=auth_headers,
|
|
|
|
|
json={
|
|
|
|
|
"items": [
|
|
|
|
|
{"product_id": products[0].id, "quantity": 2},
|
|
|
|
|
{"product_id": products[1].id, "quantity": 1},
|
|
|
|
|
],
|
|
|
|
|
"shipping_address": "123 Test St",
|
|
|
|
|
},
|
|
|
|
|
)
|
2026-02-24 14:36:31 +00:00
|
|
|
|
|
|
|
|
assert response.status_code == 201
|
|
|
|
|
data = response.get_json()
|
2026-02-24 16:19:15 +00:00
|
|
|
assert "id" in data
|
|
|
|
|
assert len(data["items"]) == 2
|
2026-02-24 14:36:31 +00:00
|
|
|
|
|
|
|
|
@pytest.mark.order
|
2026-02-24 16:19:15 +00:00
|
|
|
def test_create_order_insufficient_stock(
|
|
|
|
|
self, client, auth_headers, db_session, products
|
|
|
|
|
):
|
2026-02-24 14:36:31 +00:00
|
|
|
"""Test creating order with insufficient stock"""
|
|
|
|
|
# Set stock to 0
|
|
|
|
|
products[0].stock = 0
|
|
|
|
|
db_session.commit()
|
|
|
|
|
|
2026-02-24 16:19:15 +00:00
|
|
|
response = client.post(
|
|
|
|
|
"/api/orders",
|
|
|
|
|
headers=auth_headers,
|
|
|
|
|
json={"items": [{"product_id": products[0].id, "quantity": 2}]},
|
|
|
|
|
)
|
2026-02-24 14:36:31 +00:00
|
|
|
|
|
|
|
|
assert response.status_code == 400
|
|
|
|
|
data = response.get_json()
|
2026-02-24 16:19:15 +00:00
|
|
|
assert "insufficient" in data["error"].lower()
|
2026-02-24 14:36:31 +00:00
|
|
|
|
|
|
|
|
@pytest.mark.order
|
|
|
|
|
def test_get_single_order(self, client, auth_headers, order):
|
|
|
|
|
"""Test getting a single order"""
|
2026-02-24 16:19:15 +00:00
|
|
|
response = client.get(f"/api/orders/{order.id}", headers=auth_headers)
|
|
|
|
|
|
|
|
|
|
print("test_get_single_order", response.get_json())
|
2026-02-24 14:36:31 +00:00
|
|
|
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
data = response.get_json()
|
2026-02-24 16:19:15 +00:00
|
|
|
assert data["id"] == order.id
|
2026-02-24 14:36:31 +00:00
|
|
|
|
|
|
|
|
@pytest.mark.order
|
|
|
|
|
def test_get_other_users_order(self, client, admin_headers, regular_user, products):
|
|
|
|
|
"""Test admin accessing another user's order"""
|
|
|
|
|
# Create an order for regular_user
|
2026-02-24 16:19:15 +00:00
|
|
|
client.post(
|
|
|
|
|
"/api/auth/login",
|
|
|
|
|
json={"email": regular_user.email, "password": "password123"},
|
|
|
|
|
)
|
2026-02-24 14:36:31 +00:00
|
|
|
|
|
|
|
|
# Admin should be able to access any order
|
|
|
|
|
# This test assumes order exists, adjust as needed
|
|
|
|
|
pass
|