- Vitest hook tests for useQuantityAdjustment (optimistic updates, debouncing)
- Pytest backend tests for PATCH /items/{itemId} endpoint
- Tests cover success, failure, validation, and audit logging scenarios
166 lines
5.0 KiB
Python
166 lines
5.0 KiB
Python
"""
|
|
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
|