import pytest from fastapi.testclient import TestClient from app.schemas.models import Inventory from sqlmodel import Session @pytest.fixture(name="sample_inventory") def sample_inventory_fixture(session: Session, sample_product): """Create a sample inventory for testing.""" inventory = Inventory( product_id=sample_product.id, total_qty=100 ) session.add(inventory) session.commit() session.refresh(inventory) return inventory @pytest.fixture(name="multiple_inventories") def multiple_inventories_fixture(session: Session, multiple_products): """Create multiple inventories for testing.""" inventories = [ Inventory( product_id=multiple_products[0].id, total_qty=50 ), Inventory( product_id=multiple_products[1].id, total_qty=200 ), Inventory( product_id=multiple_products[2].id, total_qty=75 ) ] for inventory in inventories: session.add(inventory) session.commit() for inventory in inventories: session.refresh(inventory) return inventories class TestInventoryCreation: """Test inventory creation endpoints.""" def test_create_inventory_with_admin_access(self, client: TestClient, admin_token: str, sample_product): """Test inventory creation with admin token.""" inventory_data = { "product_id": sample_product.id, "total_qty": 150 } response = client.post("/api/v1/inventory/", json=inventory_data, headers={"Authorization": f"Bearer {admin_token}"}) assert response.status_code == 201 data = response.json() assert data["product_id"] == sample_product.id assert data["total_qty"] == 150 assert "id" in data def test_create_inventory_with_write_access(self, client: TestClient, write_token: str, multiple_products): """Test inventory creation with write token.""" inventory_data = { "product_id": multiple_products[0].id, "total_qty": 80 } response = client.post("/api/v1/inventory/", json=inventory_data, headers={"Authorization": f"Bearer {write_token}"}) assert response.status_code == 201 data = response.json() assert data["total_qty"] == 80 def test_create_inventory_zero_quantity(self, client: TestClient, admin_token: str, multiple_products): """Test inventory creation with zero quantity.""" inventory_data = { "product_id": multiple_products[1].id, "total_qty": 0 } response = client.post("/api/v1/inventory/", json=inventory_data, headers={"Authorization": f"Bearer {admin_token}"}) assert response.status_code == 201 data = response.json() assert data["total_qty"] == 0 def test_create_inventory_unauthorized(self, client: TestClient, sample_product): """Test inventory creation without authentication.""" inventory_data = { "product_id": sample_product.id, "total_qty": 100 } response = client.post("/api/v1/inventory/", json=inventory_data) assert response.status_code == 403 def test_create_inventory_read_only_forbidden(self, client: TestClient, read_only_token: str, sample_product): """Test inventory creation with read-only access should fail.""" inventory_data = { "product_id": sample_product.id, "total_qty": 100 } response = client.post("/api/v1/inventory/", json=inventory_data, headers={"Authorization": f"Bearer {read_only_token}"}) assert response.status_code == 403 def test_create_inventory_invalid_product(self, client: TestClient, admin_token: str): """Test creation with non-existent product should fail.""" inventory_data = { "product_id": 99999, # Non-existent product "total_qty": 100 } response = client.post("/api/v1/inventory/", json=inventory_data, headers={"Authorization": f"Bearer {admin_token}"}) assert response.status_code == 400 assert "Product not found" in response.json()["detail"] def test_create_inventory_duplicate_product(self, client: TestClient, admin_token: str, sample_inventory): """Test creation with duplicate product should fail.""" inventory_data = { "product_id": sample_inventory.product_id, # Duplicate product "total_qty": 50 } response = client.post("/api/v1/inventory/", json=inventory_data, headers={"Authorization": f"Bearer {admin_token}"}) assert response.status_code == 409 assert "Inventory entry already exists for this product" in response.json()["detail"] class TestInventoryRetrieval: """Test inventory retrieval endpoints.""" def test_get_all_inventories_with_auth(self, client: TestClient, admin_token: str, multiple_inventories): """Test retrieving all inventories with authentication.""" response = client.get("/api/v1/inventory/", headers={"Authorization": f"Bearer {admin_token}"}) assert response.status_code == 200 data = response.json() assert isinstance(data, list) assert len(data) >= 3 # At least the fixture inventories def test_get_all_inventories_read_only_access(self, client: TestClient, read_only_token: str, multiple_inventories): """Test read-only user can retrieve inventories.""" response = client.get("/api/v1/inventory/", headers={"Authorization": f"Bearer {read_only_token}"}) assert response.status_code == 200 data = response.json() assert isinstance(data, list) def test_get_inventories_unauthorized(self, client: TestClient): """Test retrieving inventories without authentication.""" response = client.get("/api/v1/inventory/") assert response.status_code == 403 def test_get_inventories_with_pagination(self, client: TestClient, admin_token: str, multiple_inventories): """Test inventory retrieval with pagination.""" response = client.get("/api/v1/inventory/?skip=0&limit=2", headers={"Authorization": f"Bearer {admin_token}"}) assert response.status_code == 200 data = response.json() assert len(data) <= 2 def test_get_single_inventory_by_id(self, client: TestClient, admin_token: str, sample_inventory): """Test retrieving a single inventory by ID.""" response = client.get(f"/api/v1/inventory/{sample_inventory.id}", headers={"Authorization": f"Bearer {admin_token}"}) assert response.status_code == 200 data = response.json() assert data["id"] == sample_inventory.id assert data["product_id"] == sample_inventory.product_id assert data["total_qty"] == sample_inventory.total_qty def test_get_nonexistent_inventory(self, client: TestClient, admin_token: str): """Test retrieving a non-existent inventory.""" response = client.get("/api/v1/inventory/99999", headers={"Authorization": f"Bearer {admin_token}"}) assert response.status_code == 404 assert "Inventory entry not found" in response.json()["detail"] def test_get_inventory_by_product(self, client: TestClient, admin_token: str, sample_inventory): """Test retrieving inventory for specific product.""" response = client.get(f"/api/v1/inventory/product/{sample_inventory.product_id}", headers={"Authorization": f"Bearer {admin_token}"}) assert response.status_code == 200 data = response.json() assert data["product_id"] == sample_inventory.product_id assert data["id"] == sample_inventory.id def test_get_inventory_by_nonexistent_product(self, client: TestClient, admin_token: str): """Test retrieving inventory for non-existent product.""" response = client.get("/api/v1/inventory/product/99999", headers={"Authorization": f"Bearer {admin_token}"}) assert response.status_code == 404 assert "Product not found" in response.json()["detail"] def test_get_inventory_by_product_no_inventory(self, client: TestClient, admin_token: str): """Test retrieving inventory for product with no inventory entry.""" # Just test with a high product ID that likely doesn't exist response = client.get("/api/v1/inventory/product/99998", headers={"Authorization": f"Bearer {admin_token}"}) assert response.status_code == 404 class TestInventoryUpdate: """Test inventory update endpoints.""" def test_update_inventory_with_write_access(self, client: TestClient, write_token: str, sample_inventory): """Test updating inventory with write access.""" update_data = { "total_qty": 175 } response = client.put(f"/api/v1/inventory/{sample_inventory.id}", json=update_data, headers={"Authorization": f"Bearer {write_token}"}) assert response.status_code == 200 data = response.json() assert data["total_qty"] == 175 assert data["product_id"] == sample_inventory.product_id # Unchanged def test_update_inventory_product_id(self, client: TestClient, admin_token: str, sample_inventory, multiple_products): """Test updating inventory product ID.""" update_data = { "product_id": multiple_products[0].id } response = client.put(f"/api/v1/inventory/{sample_inventory.id}", json=update_data, headers={"Authorization": f"Bearer {admin_token}"}) assert response.status_code == 200 data = response.json() assert data["product_id"] == multiple_products[0].id def test_update_inventory_to_zero(self, client: TestClient, admin_token: str, sample_inventory): """Test updating inventory quantity to zero.""" update_data = { "total_qty": 0 } response = client.put(f"/api/v1/inventory/{sample_inventory.id}", json=update_data, headers={"Authorization": f"Bearer {admin_token}"}) assert response.status_code == 200 data = response.json() assert data["total_qty"] == 0 def test_update_inventory_read_only_forbidden(self, client: TestClient, read_only_token: str, sample_inventory): """Test updating inventory with read-only access should fail.""" update_data = { "total_qty": 200 } response = client.put(f"/api/v1/inventory/{sample_inventory.id}", json=update_data, headers={"Authorization": f"Bearer {read_only_token}"}) assert response.status_code == 403 def test_update_inventory_invalid_product(self, client: TestClient, admin_token: str, sample_inventory): """Test updating inventory with invalid product should fail.""" update_data = { "product_id": 99999 # Non-existent product } response = client.put(f"/api/v1/inventory/{sample_inventory.id}", json=update_data, headers={"Authorization": f"Bearer {admin_token}"}) assert response.status_code == 400 assert "Product not found" in response.json()["detail"] def test_update_inventory_duplicate_product(self, client: TestClient, admin_token: str, multiple_inventories): """Test updating inventory with duplicate product should fail.""" inventory_to_update = multiple_inventories[0] existing_product_id = multiple_inventories[1].product_id update_data = { "product_id": existing_product_id } response = client.put(f"/api/v1/inventory/{inventory_to_update.id}", json=update_data, headers={"Authorization": f"Bearer {admin_token}"}) assert response.status_code == 409 assert "Inventory entry already exists for this product" in response.json()["detail"] def test_update_nonexistent_inventory(self, client: TestClient, admin_token: str): """Test updating a non-existent inventory.""" update_data = { "total_qty": 300 } response = client.put("/api/v1/inventory/99999", json=update_data, headers={"Authorization": f"Bearer {admin_token}"}) assert response.status_code == 404 assert "Inventory entry not found" in response.json()["detail"] class TestInventoryDeletion: """Test inventory deletion endpoints.""" def test_delete_inventory_with_admin_access(self, client: TestClient, admin_token: str, sample_inventory): """Test deleting inventory with admin access.""" response = client.delete(f"/api/v1/inventory/{sample_inventory.id}", headers={"Authorization": f"Bearer {admin_token}"}) assert response.status_code == 204 # Verify inventory is deleted get_response = client.get(f"/api/v1/inventory/{sample_inventory.id}", headers={"Authorization": f"Bearer {admin_token}"}) assert get_response.status_code == 404 def test_delete_inventory_write_access_forbidden(self, client: TestClient, write_token: str, sample_inventory): """Test deleting inventory with write access should fail.""" response = client.delete(f"/api/v1/inventory/{sample_inventory.id}", headers={"Authorization": f"Bearer {write_token}"}) assert response.status_code == 403 def test_delete_inventory_read_only_forbidden(self, client: TestClient, read_only_token: str, sample_inventory): """Test deleting inventory with read-only access should fail.""" response = client.delete(f"/api/v1/inventory/{sample_inventory.id}", headers={"Authorization": f"Bearer {read_only_token}"}) assert response.status_code == 403 def test_delete_nonexistent_inventory(self, client: TestClient, admin_token: str): """Test deleting a non-existent inventory.""" response = client.delete("/api/v1/inventory/99999", headers={"Authorization": f"Bearer {admin_token}"}) assert response.status_code == 404 assert "Inventory entry not found" in response.json()["detail"] def test_delete_inventory_unauthorized(self, client: TestClient, sample_inventory): """Test deleting inventory without authentication.""" response = client.delete(f"/api/v1/inventory/{sample_inventory.id}") assert response.status_code == 403 class TestInventoryValidation: """Test inventory data validation.""" def test_create_inventory_missing_required_fields(self, client: TestClient, admin_token: str): """Test creating inventory with missing required fields.""" # Missing product_id inventory_data = { "total_qty": 100 } response = client.post("/api/v1/inventory/", json=inventory_data, headers={"Authorization": f"Bearer {admin_token}"}) assert response.status_code == 422 # Validation error def test_create_inventory_negative_quantity(self, client: TestClient, admin_token: str, sample_product): """Test creating inventory with negative quantity.""" inventory_data = { "product_id": sample_product.id, "total_qty": -50 # Negative quantity } response = client.post("/api/v1/inventory/", json=inventory_data, headers={"Authorization": f"Bearer {admin_token}"}) # This might pass depending on validation rules, but business logic should prevent it # You might want to add validation in the endpoint for this def test_create_inventory_invalid_product_type(self, client: TestClient, admin_token: str): """Test creating inventory with invalid product_id type.""" inventory_data = { "product_id": "invalid_id", # String instead of int "total_qty": 100 } response = client.post("/api/v1/inventory/", json=inventory_data, headers={"Authorization": f"Bearer {admin_token}"}) assert response.status_code == 422 # Validation error def test_create_inventory_invalid_quantity_type(self, client: TestClient, admin_token: str, sample_product): """Test creating inventory with invalid quantity type.""" inventory_data = { "product_id": sample_product.id, "total_qty": "invalid_quantity" # String instead of int } response = client.post("/api/v1/inventory/", json=inventory_data, headers={"Authorization": f"Bearer {admin_token}"}) assert response.status_code == 422 # Validation error