""" Test PATCH /items/{item_id} endpoint for quantity adjustment Ensures audit logging and validation work correctly """ import pytest import json from fastapi.testclient import TestClient from sqlalchemy.orm import Session from ..models import Item, Category, AuditLog from ..schemas import Item as ItemSchema from ..database import Base, engine @pytest.fixture def test_item(db: Session): """Create a test item""" category = Category(name="Test Category") db.add(category) db.commit() item = Item( name="Test Item", category="Test Category", type="Test Type", quantity=10, barcode="TEST123", part_number="PN-TEST-001" ) db.add(item) db.commit() db.refresh(item) return item def test_patch_quantity_success(client: TestClient, test_item: Item, db: Session, auth_token: str): """Test successful quantity update with audit logging""" response = client.patch( f"/items/{test_item.id}", json={"quantity": 25}, headers={"Authorization": f"Bearer {auth_token}"} ) assert response.status_code == 200 data = response.json() assert data["quantity"] == 25 assert data["id"] == test_item.id # Verify audit log was created audit_entries = db.query(AuditLog).filter( AuditLog.target_item_id == test_item.id, AuditLog.action == "UPDATE_QUANTITY" ).all() assert len(audit_entries) == 1 audit = audit_entries[0] assert audit.quantity_change == 15 # 25 - 10 assert audit.target_item_name == "Test Item" assert audit.target_item_pn == "PN-TEST-001" assert "10 → 25" in audit.details def test_patch_quantity_to_zero(client: TestClient, test_item: Item, db: Session, auth_token: str): """Test setting quantity to zero""" response = client.patch( f"/items/{test_item.id}", json={"quantity": 0}, headers={"Authorization": f"Bearer {auth_token}"} ) assert response.status_code == 200 data = response.json() assert data["quantity"] == 0 # Verify audit log has correct delta audit = db.query(AuditLog).filter( AuditLog.target_item_id == test_item.id, AuditLog.action == "UPDATE_QUANTITY" ).first() assert audit.quantity_change == -10 # 0 - 10 def test_patch_quantity_negative_fails(client: TestClient, test_item: Item, auth_token: str): """Test that negative quantity is rejected""" response = client.patch( f"/items/{test_item.id}", json={"quantity": -5}, headers={"Authorization": f"Bearer {auth_token}"} ) assert response.status_code == 400 assert "non-negative" in response.json()["detail"].lower() def test_patch_quantity_missing_field(client: TestClient, test_item: Item, auth_token: str): """Test that missing quantity field is rejected""" response = client.patch( f"/items/{test_item.id}", json={}, headers={"Authorization": f"Bearer {auth_token}"} ) assert response.status_code == 400 assert "quantity" in response.json()["detail"].lower() def test_patch_quantity_invalid_type(client: TestClient, test_item: Item, auth_token: str): """Test that non-integer quantity is rejected""" response = client.patch( f"/items/{test_item.id}", json={"quantity": "not a number"}, headers={"Authorization": f"Bearer {auth_token}"} ) assert response.status_code == 400 assert "integer" in response.json()["detail"].lower() def test_patch_quantity_not_found(client: TestClient, auth_token: str): """Test that updating non-existent item returns 404""" response = client.patch( "/items/99999", json={"quantity": 10}, headers={"Authorization": f"Bearer {auth_token}"} ) assert response.status_code == 404 assert "not found" in response.json()["detail"].lower() def test_patch_quantity_no_auth(client: TestClient, test_item: Item): """Test that unauthenticated request is rejected""" response = client.patch( f"/items/{test_item.id}", json={"quantity": 25} ) assert response.status_code == 401 def test_patch_quantity_audit_fields(client: TestClient, test_item: Item, db: Session, auth_token: str, current_user_id: str): """Test that audit log contains all required fields""" response = client.patch( f"/items/{test_item.id}", json={"quantity": 30}, headers={"Authorization": f"Bearer {auth_token}"} ) assert response.status_code == 200 audit = db.query(AuditLog).filter( AuditLog.target_item_id == test_item.id, AuditLog.action == "UPDATE_QUANTITY" ).first() assert audit is not None assert audit.user_id == current_user_id assert audit.action == "UPDATE_QUANTITY" assert audit.target_item_id == test_item.id assert audit.target_item_name == "Test Item" assert audit.target_item_pn == "PN-TEST-001" assert audit.target_item_barcode == "TEST123" assert audit.quantity_change == 20 # 30 - 10 assert "→" in audit.details # Direction indicator in details