working with login and building inventory
This commit is contained in:
191
trade-hub/backend/tests/test_inventory.py
Normal file
191
trade-hub/backend/tests/test_inventory.py
Normal file
@@ -0,0 +1,191 @@
|
||||
"""Personal inventory tests: auth gating, CRUD, isolation, catalog immutability."""
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from app.api import app
|
||||
|
||||
PASSWORD = "correct horse battery staple"
|
||||
CARROCK = "ANVL_Carrack"
|
||||
RIFLE = "BEHR_Rifle_Ballistic_01"
|
||||
|
||||
|
||||
def authed_client(username: str = "alice") -> TestClient:
|
||||
client = TestClient(app)
|
||||
resp = client.post(
|
||||
"/api/v1/auth/register", json={"username": username, "password": PASSWORD}
|
||||
)
|
||||
assert resp.status_code == 201, resp.text
|
||||
return client
|
||||
|
||||
|
||||
def add(client: TestClient, class_name: str, quantity: int = 1, notes: str = ""):
|
||||
return client.post(
|
||||
"/api/v1/me/inventory",
|
||||
json={"class_name": class_name, "quantity": quantity, "notes": notes},
|
||||
)
|
||||
|
||||
|
||||
def test_new_account_starts_with_empty_inventory():
|
||||
client = authed_client("freshpilot")
|
||||
listing = client.get("/api/v1/me/inventory").json()
|
||||
assert listing["total"] == 0
|
||||
assert listing["items"] == []
|
||||
assert client.get("/api/v1/me/inventory/summary").json() == {
|
||||
"items": 0,
|
||||
"quantity": 0,
|
||||
"categories": {},
|
||||
"unavailable": 0,
|
||||
}
|
||||
|
||||
|
||||
def test_inventory_requires_authentication():
|
||||
client = TestClient(app)
|
||||
assert client.get("/api/v1/me/inventory").status_code == 401
|
||||
assert add(client, CARROCK).status_code == 401
|
||||
assert client.get("/api/v1/me/inventory/summary").status_code == 401
|
||||
|
||||
|
||||
def test_add_unknown_catalog_item_is_not_found():
|
||||
client = authed_client()
|
||||
assert add(client, "NOPE_Does_Not_Exist").status_code == 404
|
||||
|
||||
|
||||
def test_add_creates_enriched_entry():
|
||||
client = authed_client()
|
||||
resp = add(client, CARROCK)
|
||||
assert resp.status_code == 201
|
||||
body = resp.json()
|
||||
assert body["created"] is True
|
||||
entry = body["entry"]
|
||||
assert entry["class_name"] == CARROCK
|
||||
assert entry["name"] == "Anvil Carrack"
|
||||
assert entry["category"] == "ships"
|
||||
assert entry["quantity"] == 1
|
||||
assert entry["available"] is True
|
||||
assert entry["manufacturer"]["code"] == "ANVL"
|
||||
|
||||
|
||||
def test_add_same_item_increments_quantity():
|
||||
client = authed_client()
|
||||
assert add(client, CARROCK, quantity=1).status_code == 201
|
||||
resp = add(client, CARROCK, quantity=2, notes="pledged")
|
||||
assert resp.status_code == 200
|
||||
body = resp.json()
|
||||
assert body["created"] is False
|
||||
assert body["entry"]["quantity"] == 3
|
||||
assert body["entry"]["notes"] == "pledged"
|
||||
|
||||
listing = client.get("/api/v1/me/inventory").json()
|
||||
assert listing["total"] == 1
|
||||
|
||||
|
||||
def test_inventories_are_isolated_between_users():
|
||||
alice = authed_client("alice")
|
||||
bob = authed_client("bob")
|
||||
|
||||
assert add(alice, CARROCK).status_code == 201
|
||||
assert add(bob, RIFLE).status_code == 201
|
||||
|
||||
alice_items = alice.get("/api/v1/me/inventory").json()["items"]
|
||||
bob_items = bob.get("/api/v1/me/inventory").json()["items"]
|
||||
|
||||
assert [i["class_name"] for i in alice_items] == [CARROCK]
|
||||
assert [i["class_name"] for i in bob_items] == [RIFLE]
|
||||
|
||||
|
||||
def test_patch_updates_quantity_and_notes():
|
||||
client = authed_client()
|
||||
assert add(client, CARROCK).status_code == 201
|
||||
|
||||
resp = client.patch(
|
||||
f"/api/v1/me/inventory/{CARROCK}", json={"quantity": 4, "notes": "hangar 3"}
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
body = resp.json()
|
||||
assert body["quantity"] == 4
|
||||
assert body["notes"] == "hangar 3"
|
||||
|
||||
|
||||
def test_patch_unknown_entry_is_not_found():
|
||||
client = authed_client()
|
||||
assert client.patch(f"/api/v1/me/inventory/{CARROCK}", json={"quantity": 2}).status_code == 404
|
||||
|
||||
|
||||
def test_patch_rejects_zero_quantity():
|
||||
client = authed_client()
|
||||
assert add(client, CARROCK).status_code == 201
|
||||
resp = client.patch(f"/api/v1/me/inventory/{CARROCK}", json={"quantity": 0})
|
||||
assert resp.status_code == 422
|
||||
|
||||
|
||||
def test_delete_removes_entry():
|
||||
client = authed_client()
|
||||
assert add(client, CARROCK).status_code == 201
|
||||
|
||||
assert client.delete(f"/api/v1/me/inventory/{CARROCK}").status_code == 204
|
||||
assert client.get("/api/v1/me/inventory").json()["total"] == 0
|
||||
assert client.delete(f"/api/v1/me/inventory/{CARROCK}").status_code == 404
|
||||
|
||||
|
||||
def test_list_filters_by_category_and_search():
|
||||
client = authed_client()
|
||||
assert add(client, CARROCK).status_code == 201
|
||||
assert add(client, RIFLE).status_code == 201
|
||||
|
||||
ships = client.get("/api/v1/me/inventory", params={"category": "ships"}).json()
|
||||
assert ships["total"] == 1
|
||||
assert ships["items"][0]["class_name"] == CARROCK
|
||||
|
||||
found = client.get("/api/v1/me/inventory", params={"search": "carrack"}).json()
|
||||
assert found["total"] == 1
|
||||
assert found["items"][0]["class_name"] == CARROCK
|
||||
|
||||
|
||||
def test_list_pagination_envelope_and_cap():
|
||||
client = authed_client()
|
||||
assert add(client, CARROCK).status_code == 201
|
||||
assert add(client, RIFLE).status_code == 201
|
||||
|
||||
body = client.get(
|
||||
"/api/v1/me/inventory", params={"page_size": 1, "page": 2}
|
||||
).json()
|
||||
assert body["total"] == 2
|
||||
assert body["page"] == 2
|
||||
assert body["page_size"] == 1
|
||||
assert body["total_pages"] == 2
|
||||
assert len(body["items"]) == 1
|
||||
|
||||
capped = client.get("/api/v1/me/inventory", params={"page_size": 500}).json()
|
||||
assert capped["page_size"] == 100
|
||||
|
||||
|
||||
def test_summary_aggregates_quantity_and_categories():
|
||||
client = authed_client()
|
||||
assert add(client, CARROCK, quantity=2).status_code == 201
|
||||
assert add(client, RIFLE, quantity=3).status_code == 201
|
||||
|
||||
summary = client.get("/api/v1/me/inventory/summary").json()
|
||||
assert summary["items"] == 2
|
||||
assert summary["quantity"] == 5
|
||||
assert summary["unavailable"] == 0
|
||||
assert summary["categories"]["ships"] == 2
|
||||
|
||||
|
||||
def test_notes_length_is_bounded():
|
||||
client = authed_client()
|
||||
resp = add(client, CARROCK, notes="x" * 1001)
|
||||
assert resp.status_code == 422
|
||||
|
||||
|
||||
def test_catalog_is_not_mutated_by_inventory_operations():
|
||||
client = authed_client()
|
||||
before = client.get("/api/v1/items", params={"page_size": 100}).json()
|
||||
|
||||
assert add(client, CARROCK).status_code == 201
|
||||
client.patch(f"/api/v1/me/inventory/{CARROCK}", json={"quantity": 9})
|
||||
client.delete(f"/api/v1/me/inventory/{CARROCK}")
|
||||
|
||||
after = client.get("/api/v1/items", params={"page_size": 100}).json()
|
||||
assert after == before
|
||||
carrack = client.get(f"/api/v1/items/{CARROCK}").json()
|
||||
assert "quantity" not in carrack
|
||||
assert "notes" not in carrack
|
||||
Reference in New Issue
Block a user