from __future__ import annotations
import httpx
import pytest
@pytest.mark.integration
def test_create_all_three_lifecycle_types(api_client: httpx.Client, create_test_user):
"""
Scenario: Support for all 3 MVP lifecycle types (Renewals, Maintenance, Warranties).
Creates one of each type and verifies response schemas.
"""
user = create_test_user(email_prefix="items_crud")
# 1. Renewal with due date
res_renewal = api_client.post(
"/api/v1/items",
json={"name": "Passport Renewal", "lifecycleType": "renewal", "dueDate": "2026-11-01"},
headers=user["headers"],
)
assert res_renewal.status_code == 201
assert res_renewal.json()["lifecycleType"] == "renewal"
assert res_renewal.json()["dueDate"] == "2026-11-01"
# 2. Maintenance with optional (missing) due date
res_maint = api_client.post(
"/api/v1/items",
json={"name": "HVAC Filter Inspection", "lifecycleType": "maintenance"},
headers=user["headers"],
)
assert res_maint.status_code == 201
assert res_maint.json()["lifecycleType"] == "maintenance"
assert res_maint.json()["dueDate"] is None
# 3. Warranty with future due date
res_warr = api_client.post(
"/api/v1/items",
json={"name": "OLED TV 3-Year Protection", "lifecycleType": "warranty", "dueDate": "2028-12-31"},
headers=user["headers"],
)
assert res_warr.status_code == 201
assert res_warr.json()["lifecycleType"] == "warranty"
@pytest.mark.integration
def test_list_items_and_filtering(api_client: httpx.Client, create_test_user):
"""
Scenario: Listing, type filtering, and query search on items.
"""
user = create_test_user(email_prefix="items_filter")
# Seed 3 items
api_client.post(
"/api/v1/items",
json={"name": "Auto Insurance Renewal", "lifecycleType": "renewal", "dueDate": "2026-10-30"},
headers=user["headers"],
)
api_client.post(
"/api/v1/items",
json={"name": "Car Oil Change", "lifecycleType": "maintenance", "dueDate": "2026-11-10"},
headers=user["headers"],
)
api_client.post(
"/api/v1/items",
json={"name": "Blender Warranty", "lifecycleType": "warranty", "dueDate": "2027-01-15"},
headers=user["headers"],
)
# List all items for user
res_all = api_client.get("/api/v1/items", headers=user["headers"])
assert res_all.status_code == 200
items = res_all.json()
assert len(items) >= 3
# Filter by type=renewal
res_ren = api_client.get("/api/v1/items?type=renewal", headers=user["headers"])
assert res_ren.status_code == 200
renewal_items = res_ren.json()
assert all(it["lifecycleType"] == "renewal" for it in renewal_items)
assert any(it["name"] == "Auto Insurance Renewal" for it in renewal_items)
# Search query
res_search = api_client.get("/api/v1/items?searchQuery=Blender", headers=user["headers"])
assert res_search.status_code == 200
search_items = res_search.json()
assert len(search_items) == 1
assert search_items[0]["name"] == "Blender Warranty"
@pytest.mark.integration
def test_get_and_patch_item(api_client: httpx.Client, create_test_user):
"""
Scenario: Item retrieval by ID and updating fields.
"""
user = create_test_user(email_prefix="items_patch")
create_res = api_client.post(
"/api/v1/items",
json={"name": "Initial Item Name", "lifecycleType": "renewal", "dueDate": "2026-10-20"},
headers=user["headers"],
)
item_id = create_res.json()["id"]
# Retrieve by ID
get_res = api_client.get(f"/api/v1/items/{item_id}", headers=user["headers"])
assert get_res.status_code == 200
assert get_res.json()["id"] == item_id
# Update name and due date
patch_res = api_client.patch(
f"/api/v1/items/{item_id}",
json={"name": "Updated Item Name", "dueDate": "2026-12-25"},
headers=user["headers"],
)
assert patch_res.status_code == 200
assert patch_res.json()["name"] == "Updated Item Name"
assert patch_res.json()["dueDate"] == "2026-12-25"
@pytest.mark.integration
def test_toggle_item_completion(api_client: httpx.Client, create_test_user):
"""
Scenario: Completion toggle and retention in main list.
Completed items remain retained in list and can be toggled back.
"""
user = create_test_user(email_prefix="items_complete")
create_res = api_client.post(
"/api/v1/items",
json={"name": "Dryer Vent Cleaning", "lifecycleType": "maintenance", "dueDate": "2026-10-15"},
headers=user["headers"],
)
item_id = create_res.json()["id"]
assert create_res.json()["status"] == "active"
# Toggle to complete
comp_res = api_client.post(f"/api/v1/items/{item_id}/complete", headers=user["headers"])
assert comp_res.status_code == 200
assert comp_res.json()["status"] == "completed"
# Verify item is retained in list
list_res = api_client.get("/api/v1/items", headers=user["headers"])
assert list_res.status_code == 200
completed_item = next(it for it in list_res.json() if it["id"] == item_id)
assert completed_item["status"] == "completed"
# Toggle back to active
toggle_back = api_client.post(f"/api/v1/items/{item_id}/complete", headers=user["headers"])
assert toggle_back.status_code == 200
assert toggle_back.json()["status"] == "active"
@pytest.mark.integration
def test_delete_item_permanently(api_client: httpx.Client, create_test_user):
"""
Scenario: Permanent item deletion.
"""
user = create_test_user(email_prefix="items_del")
create_res = api_client.post(
"/api/v1/items",
json={"name": "Item To Delete", "lifecycleType": "warranty"},
headers=user["headers"],
)
item_id = create_res.json()["id"]
# Delete
del_res = api_client.delete(f"/api/v1/items/{item_id}", headers=user["headers"])
assert del_res.status_code == 204
# Subsequent GET returns 404
get_res = api_client.get(f"/api/v1/items/{item_id}", headers=user["headers"])
assert get_res.status_code == 404
@pytest.mark.integration
def test_item_validation_errors(api_client: httpx.Client, create_test_user):
"""
Scenario: Input validation and bad request handling.
"""
user = create_test_user(email_prefix="items_invalid")
# Invalid lifecycleType
bad_type = api_client.post(
"/api/v1/items",
json={"name": "Bad Item", "lifecycleType": "invalid_type"},
headers=user["headers"],
)
assert bad_type.status_code in (400, 422)
# Missing required name
bad_name = api_client.post(
"/api/v1/items",
json={"lifecycleType": "renewal"},
headers=user["headers"],
)
assert bad_name.status_code in (400, 422)