fix: resolve frontend/backend integration bugs
- Rename skincare route prefix /skin-snapshots → /skincare to match API client - Add redirect_slashes=False to FastAPI app; change collection routes from "/" to "" to eliminate 307 redirects on POST/GET without trailing slash - Fix redirect() inside try/catch in products/new and routines/new server actions (SvelteKit redirect() throws and was being caught as a 500 error) - Eagerly load inventory and steps relationships via explicit SELECT + model_dump(mode="json"), working around SQLModel 0.0.37 not serializing Relationship fields in response_model - Add field_validator for product_effect_profile to coerce DB-returned dict → ProductEffectProfile, eliminating Pydantic serializer warning - Update all tests to use routes without trailing slash Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
8d4f9d1fc6
commit
9bf94a979c
11 changed files with 85 additions and 68 deletions
|
|
@ -188,7 +188,7 @@ def get_or_404(session: Session, model, record_id) -> object:
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
@router.get("/", response_model=list[Product])
|
@router.get("", response_model=list[Product])
|
||||||
def list_products(
|
def list_products(
|
||||||
category: Optional[ProductCategory] = None,
|
category: Optional[ProductCategory] = None,
|
||||||
brand: Optional[str] = None,
|
brand: Optional[str] = None,
|
||||||
|
|
@ -224,7 +224,7 @@ def list_products(
|
||||||
return products
|
return products
|
||||||
|
|
||||||
|
|
||||||
@router.post("/", response_model=Product, status_code=201)
|
@router.post("", response_model=Product, status_code=201)
|
||||||
def create_product(data: ProductCreate, session: Session = Depends(get_session)):
|
def create_product(data: ProductCreate, session: Session = Depends(get_session)):
|
||||||
product = Product(
|
product = Product(
|
||||||
id=uuid4(),
|
id=uuid4(),
|
||||||
|
|
@ -236,9 +236,13 @@ def create_product(data: ProductCreate, session: Session = Depends(get_session))
|
||||||
return product
|
return product
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{product_id}", response_model=Product)
|
@router.get("/{product_id}")
|
||||||
def get_product(product_id: UUID, session: Session = Depends(get_session)):
|
def get_product(product_id: UUID, session: Session = Depends(get_session)):
|
||||||
return get_or_404(session, Product, product_id)
|
product = get_or_404(session, Product, product_id)
|
||||||
|
inventory = session.exec(select(ProductInventory).where(ProductInventory.product_id == product_id)).all()
|
||||||
|
data = product.model_dump(mode="json")
|
||||||
|
data["inventory"] = [item.model_dump(mode="json") for item in inventory]
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
@router.patch("/{product_id}", response_model=Product)
|
@router.patch("/{product_id}", response_model=Product)
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,7 @@ def get_or_404(session: Session, model, record_id) -> object:
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
@router.get("/", response_model=list[Routine])
|
@router.get("", response_model=list[Routine])
|
||||||
def list_routines(
|
def list_routines(
|
||||||
from_date: Optional[date] = None,
|
from_date: Optional[date] = None,
|
||||||
to_date: Optional[date] = None,
|
to_date: Optional[date] = None,
|
||||||
|
|
@ -93,7 +93,7 @@ def list_routines(
|
||||||
return session.exec(stmt).all()
|
return session.exec(stmt).all()
|
||||||
|
|
||||||
|
|
||||||
@router.post("/", response_model=Routine, status_code=201)
|
@router.post("", response_model=Routine, status_code=201)
|
||||||
def create_routine(data: RoutineCreate, session: Session = Depends(get_session)):
|
def create_routine(data: RoutineCreate, session: Session = Depends(get_session)):
|
||||||
routine = Routine(id=uuid4(), **data.model_dump())
|
routine = Routine(id=uuid4(), **data.model_dump())
|
||||||
session.add(routine)
|
session.add(routine)
|
||||||
|
|
@ -108,9 +108,13 @@ def list_grooming_schedule(session: Session = Depends(get_session)):
|
||||||
return session.exec(select(GroomingSchedule)).all()
|
return session.exec(select(GroomingSchedule)).all()
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{routine_id}", response_model=Routine)
|
@router.get("/{routine_id}")
|
||||||
def get_routine(routine_id: UUID, session: Session = Depends(get_session)):
|
def get_routine(routine_id: UUID, session: Session = Depends(get_session)):
|
||||||
return get_or_404(session, Routine, routine_id)
|
routine = get_or_404(session, Routine, routine_id)
|
||||||
|
steps = session.exec(select(RoutineStep).where(RoutineStep.routine_id == routine_id)).all()
|
||||||
|
data = routine.model_dump(mode="json")
|
||||||
|
data["steps"] = [step.model_dump(mode="json") for step in steps]
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
@router.patch("/{routine_id}", response_model=Routine)
|
@router.patch("/{routine_id}", response_model=Routine)
|
||||||
|
|
|
||||||
|
|
@ -78,7 +78,7 @@ def get_or_404(session: Session, model, record_id) -> object:
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
@router.get("/", response_model=list[SkinConditionSnapshot])
|
@router.get("", response_model=list[SkinConditionSnapshot])
|
||||||
def list_snapshots(
|
def list_snapshots(
|
||||||
from_date: Optional[date] = None,
|
from_date: Optional[date] = None,
|
||||||
to_date: Optional[date] = None,
|
to_date: Optional[date] = None,
|
||||||
|
|
@ -95,7 +95,7 @@ def list_snapshots(
|
||||||
return session.exec(stmt).all()
|
return session.exec(stmt).all()
|
||||||
|
|
||||||
|
|
||||||
@router.post("/", response_model=SkinConditionSnapshot, status_code=201)
|
@router.post("", response_model=SkinConditionSnapshot, status_code=201)
|
||||||
def create_snapshot(
|
def create_snapshot(
|
||||||
data: SnapshotCreate, session: Session = Depends(get_session)
|
data: SnapshotCreate, session: Session = Depends(get_session)
|
||||||
):
|
):
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ from datetime import date, datetime
|
||||||
from typing import ClassVar, Optional
|
from typing import ClassVar, Optional
|
||||||
from uuid import UUID, uuid4
|
from uuid import UUID, uuid4
|
||||||
|
|
||||||
from pydantic import model_validator
|
from pydantic import field_validator, model_validator
|
||||||
from sqlalchemy import JSON, Column, DateTime
|
from sqlalchemy import JSON, Column, DateTime
|
||||||
from sqlmodel import Field, Relationship, SQLModel
|
from sqlmodel import Field, Relationship, SQLModel
|
||||||
|
|
||||||
|
|
@ -188,6 +188,13 @@ class Product(SQLModel, table=True):
|
||||||
|
|
||||||
inventory: list["ProductInventory"] = Relationship(back_populates="product")
|
inventory: list["ProductInventory"] = Relationship(back_populates="product")
|
||||||
|
|
||||||
|
@field_validator("product_effect_profile", mode="before")
|
||||||
|
@classmethod
|
||||||
|
def coerce_effect_profile(cls, v: object) -> object:
|
||||||
|
if isinstance(v, dict):
|
||||||
|
return ProductEffectProfile(**v)
|
||||||
|
return v
|
||||||
|
|
||||||
@model_validator(mode="after")
|
@model_validator(mode="after")
|
||||||
def validate_business_rules(self) -> "Product":
|
def validate_business_rules(self) -> "Product":
|
||||||
if (
|
if (
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ async def lifespan(app: FastAPI):
|
||||||
yield
|
yield
|
||||||
|
|
||||||
|
|
||||||
app = FastAPI(title="innercontext API", lifespan=lifespan)
|
app = FastAPI(title="innercontext API", lifespan=lifespan, redirect_slashes=False)
|
||||||
|
|
||||||
app.add_middleware(
|
app.add_middleware(
|
||||||
CORSMiddleware,
|
CORSMiddleware,
|
||||||
|
|
@ -30,7 +30,7 @@ app.include_router(products.router, prefix="/products", tags=["products"])
|
||||||
app.include_router(inventory.router, prefix="/inventory", tags=["inventory"])
|
app.include_router(inventory.router, prefix="/inventory", tags=["inventory"])
|
||||||
app.include_router(health.router, prefix="/health", tags=["health"])
|
app.include_router(health.router, prefix="/health", tags=["health"])
|
||||||
app.include_router(routines.router, prefix="/routines", tags=["routines"])
|
app.include_router(routines.router, prefix="/routines", tags=["routines"])
|
||||||
app.include_router(skincare.router, prefix="/skin-snapshots", tags=["skincare"])
|
app.include_router(skincare.router, prefix="/skincare", tags=["skincare"])
|
||||||
|
|
||||||
|
|
||||||
@app.get("/health-check")
|
@app.get("/health-check")
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,7 @@ def product_data():
|
||||||
|
|
||||||
@pytest.fixture()
|
@pytest.fixture()
|
||||||
def created_product(client, product_data):
|
def created_product(client, product_data):
|
||||||
r = client.post("/products/", json=product_data)
|
r = client.post("/products", json=product_data)
|
||||||
assert r.status_code == 201
|
assert r.status_code == 201
|
||||||
return r.json()
|
return r.json()
|
||||||
|
|
||||||
|
|
@ -81,7 +81,7 @@ def created_medication(client, medication_data):
|
||||||
@pytest.fixture()
|
@pytest.fixture()
|
||||||
def created_routine(client):
|
def created_routine(client):
|
||||||
r = client.post(
|
r = client.post(
|
||||||
"/routines/", json={"routine_date": "2026-02-26", "part_of_day": "am"}
|
"/routines", json={"routine_date": "2026-02-26", "part_of_day": "am"}
|
||||||
)
|
)
|
||||||
assert r.status_code == 201
|
assert r.status_code == 201
|
||||||
return r.json()
|
return r.json()
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import uuid
|
||||||
|
|
||||||
|
|
||||||
def test_create_minimal(client, product_data):
|
def test_create_minimal(client, product_data):
|
||||||
r = client.post("/products/", json=product_data)
|
r = client.post("/products", json=product_data)
|
||||||
assert r.status_code == 201
|
assert r.status_code == 201
|
||||||
data = r.json()
|
data = r.json()
|
||||||
assert "id" in data
|
assert "id" in data
|
||||||
|
|
@ -15,7 +15,7 @@ def test_create_with_actives(client, product_data):
|
||||||
product_data["actives"] = [
|
product_data["actives"] = [
|
||||||
{"name": "Niacinamide", "percent": 10.0, "functions": ["niacinamide"]}
|
{"name": "Niacinamide", "percent": 10.0, "functions": ["niacinamide"]}
|
||||||
]
|
]
|
||||||
r = client.post("/products/", json=product_data)
|
r = client.post("/products", json=product_data)
|
||||||
assert r.status_code == 201
|
assert r.status_code == 201
|
||||||
data = r.json()
|
data = r.json()
|
||||||
assert len(data["actives"]) == 1
|
assert len(data["actives"]) == 1
|
||||||
|
|
@ -25,24 +25,24 @@ def test_create_with_actives(client, product_data):
|
||||||
|
|
||||||
def test_create_invalid_enum(client, product_data):
|
def test_create_invalid_enum(client, product_data):
|
||||||
product_data["category"] = "not_a_category"
|
product_data["category"] = "not_a_category"
|
||||||
r = client.post("/products/", json=product_data)
|
r = client.post("/products", json=product_data)
|
||||||
assert r.status_code == 422
|
assert r.status_code == 422
|
||||||
|
|
||||||
|
|
||||||
def test_create_missing_required(client, product_data):
|
def test_create_missing_required(client, product_data):
|
||||||
del product_data["name"]
|
del product_data["name"]
|
||||||
r = client.post("/products/", json=product_data)
|
r = client.post("/products", json=product_data)
|
||||||
assert r.status_code == 422
|
assert r.status_code == 422
|
||||||
|
|
||||||
|
|
||||||
def test_list_empty(client):
|
def test_list_empty(client):
|
||||||
r = client.get("/products/")
|
r = client.get("/products")
|
||||||
assert r.status_code == 200
|
assert r.status_code == 200
|
||||||
assert r.json() == []
|
assert r.json() == []
|
||||||
|
|
||||||
|
|
||||||
def test_list_returns_created(client, created_product):
|
def test_list_returns_created(client, created_product):
|
||||||
r = client.get("/products/")
|
r = client.get("/products")
|
||||||
assert r.status_code == 200
|
assert r.status_code == 200
|
||||||
ids = [p["id"] for p in r.json()]
|
ids = [p["id"] for p in r.json()]
|
||||||
assert created_product["id"] in ids
|
assert created_product["id"] in ids
|
||||||
|
|
@ -56,12 +56,12 @@ def test_list_filter_category(client, client_and_data=None):
|
||||||
"recommended_time": "both",
|
"recommended_time": "both",
|
||||||
"leave_on": True,
|
"leave_on": True,
|
||||||
}
|
}
|
||||||
r1 = client.post("/products/", json={**base, "name": "Moist", "category": "moisturizer"})
|
r1 = client.post("/products", json={**base, "name": "Moist", "category": "moisturizer"})
|
||||||
r2 = client.post("/products/", json={**base, "name": "Ser", "category": "serum"})
|
r2 = client.post("/products", json={**base, "name": "Ser", "category": "serum"})
|
||||||
assert r1.status_code == 201
|
assert r1.status_code == 201
|
||||||
assert r2.status_code == 201
|
assert r2.status_code == 201
|
||||||
|
|
||||||
r = client.get("/products/?category=moisturizer")
|
r = client.get("/products?category=moisturizer")
|
||||||
assert r.status_code == 200
|
assert r.status_code == 200
|
||||||
data = r.json()
|
data = r.json()
|
||||||
assert all(p["category"] == "moisturizer" for p in data)
|
assert all(p["category"] == "moisturizer" for p in data)
|
||||||
|
|
@ -77,10 +77,10 @@ def test_list_filter_brand(client):
|
||||||
"leave_on": True,
|
"leave_on": True,
|
||||||
"category": "serum",
|
"category": "serum",
|
||||||
}
|
}
|
||||||
client.post("/products/", json={**base, "name": "A1", "brand": "BrandA"})
|
client.post("/products", json={**base, "name": "A1", "brand": "BrandA"})
|
||||||
client.post("/products/", json={**base, "name": "B1", "brand": "BrandB"})
|
client.post("/products", json={**base, "name": "B1", "brand": "BrandB"})
|
||||||
|
|
||||||
r = client.get("/products/?brand=BrandA")
|
r = client.get("/products?brand=BrandA")
|
||||||
assert r.status_code == 200
|
assert r.status_code == 200
|
||||||
data = r.json()
|
data = r.json()
|
||||||
assert all(p["brand"] == "BrandA" for p in data)
|
assert all(p["brand"] == "BrandA" for p in data)
|
||||||
|
|
@ -95,14 +95,14 @@ def test_list_filter_is_medication(client):
|
||||||
"leave_on": True,
|
"leave_on": True,
|
||||||
"category": "serum",
|
"category": "serum",
|
||||||
}
|
}
|
||||||
client.post("/products/", json={**base, "name": "Normal", "is_medication": False})
|
client.post("/products", json={**base, "name": "Normal", "is_medication": False})
|
||||||
# is_medication=True requires usage_notes (model validator)
|
# is_medication=True requires usage_notes (model validator)
|
||||||
client.post(
|
client.post(
|
||||||
"/products/",
|
"/products",
|
||||||
json={**base, "name": "Med", "is_medication": True, "usage_notes": "Apply pea-sized amount"},
|
json={**base, "name": "Med", "is_medication": True, "usage_notes": "Apply pea-sized amount"},
|
||||||
)
|
)
|
||||||
|
|
||||||
r = client.get("/products/?is_medication=true")
|
r = client.get("/products?is_medication=true")
|
||||||
assert r.status_code == 200
|
assert r.status_code == 200
|
||||||
data = r.json()
|
data = r.json()
|
||||||
assert all(p["is_medication"] is True for p in data)
|
assert all(p["is_medication"] is True for p in data)
|
||||||
|
|
@ -118,10 +118,10 @@ def test_list_filter_targets(client):
|
||||||
"leave_on": True,
|
"leave_on": True,
|
||||||
"category": "serum",
|
"category": "serum",
|
||||||
}
|
}
|
||||||
client.post("/products/", json={**base, "name": "Acne", "targets": ["acne"]})
|
client.post("/products", json={**base, "name": "Acne", "targets": ["acne"]})
|
||||||
client.post("/products/", json={**base, "name": "Aging", "targets": ["aging"]})
|
client.post("/products", json={**base, "name": "Aging", "targets": ["aging"]})
|
||||||
|
|
||||||
r = client.get("/products/?targets=acne")
|
r = client.get("/products?targets=acne")
|
||||||
assert r.status_code == 200
|
assert r.status_code == 200
|
||||||
data = r.json()
|
data = r.json()
|
||||||
assert len(data) == 1
|
assert len(data) == 1
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import uuid
|
||||||
|
|
||||||
def test_create_routine_minimal(client):
|
def test_create_routine_minimal(client):
|
||||||
r = client.post(
|
r = client.post(
|
||||||
"/routines/", json={"routine_date": "2026-02-26", "part_of_day": "am"}
|
"/routines", json={"routine_date": "2026-02-26", "part_of_day": "am"}
|
||||||
)
|
)
|
||||||
assert r.status_code == 201
|
assert r.status_code == 201
|
||||||
data = r.json()
|
data = r.json()
|
||||||
|
|
@ -19,21 +19,21 @@ def test_create_routine_minimal(client):
|
||||||
|
|
||||||
def test_create_routine_invalid_part_of_day(client):
|
def test_create_routine_invalid_part_of_day(client):
|
||||||
r = client.post(
|
r = client.post(
|
||||||
"/routines/", json={"routine_date": "2026-02-26", "part_of_day": "noon"}
|
"/routines", json={"routine_date": "2026-02-26", "part_of_day": "noon"}
|
||||||
)
|
)
|
||||||
assert r.status_code == 422
|
assert r.status_code == 422
|
||||||
|
|
||||||
|
|
||||||
def test_list_routines_empty(client):
|
def test_list_routines_empty(client):
|
||||||
r = client.get("/routines/")
|
r = client.get("/routines")
|
||||||
assert r.status_code == 200
|
assert r.status_code == 200
|
||||||
assert r.json() == []
|
assert r.json() == []
|
||||||
|
|
||||||
|
|
||||||
def test_list_filter_date_range(client):
|
def test_list_filter_date_range(client):
|
||||||
client.post("/routines/", json={"routine_date": "2026-01-01", "part_of_day": "am"})
|
client.post("/routines", json={"routine_date": "2026-01-01", "part_of_day": "am"})
|
||||||
client.post("/routines/", json={"routine_date": "2026-06-01", "part_of_day": "am"})
|
client.post("/routines", json={"routine_date": "2026-06-01", "part_of_day": "am"})
|
||||||
r = client.get("/routines/?from_date=2026-05-01&to_date=2026-12-31")
|
r = client.get("/routines?from_date=2026-05-01&to_date=2026-12-31")
|
||||||
assert r.status_code == 200
|
assert r.status_code == 200
|
||||||
data = r.json()
|
data = r.json()
|
||||||
assert len(data) == 1
|
assert len(data) == 1
|
||||||
|
|
@ -41,9 +41,9 @@ def test_list_filter_date_range(client):
|
||||||
|
|
||||||
|
|
||||||
def test_list_filter_part_of_day(client):
|
def test_list_filter_part_of_day(client):
|
||||||
client.post("/routines/", json={"routine_date": "2026-02-01", "part_of_day": "am"})
|
client.post("/routines", json={"routine_date": "2026-02-01", "part_of_day": "am"})
|
||||||
client.post("/routines/", json={"routine_date": "2026-02-02", "part_of_day": "pm"})
|
client.post("/routines", json={"routine_date": "2026-02-02", "part_of_day": "pm"})
|
||||||
r = client.get("/routines/?part_of_day=pm")
|
r = client.get("/routines?part_of_day=pm")
|
||||||
assert r.status_code == 200
|
assert r.status_code == 200
|
||||||
data = r.json()
|
data = r.json()
|
||||||
assert len(data) == 1
|
assert len(data) == 1
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import uuid
|
||||||
|
|
||||||
|
|
||||||
def test_create_snapshot_minimal(client):
|
def test_create_snapshot_minimal(client):
|
||||||
r = client.post("/skin-snapshots/", json={"snapshot_date": "2026-02-26"})
|
r = client.post("/skincare", json={"snapshot_date": "2026-02-26"})
|
||||||
assert r.status_code == 201
|
assert r.status_code == 201
|
||||||
data = r.json()
|
data = r.json()
|
||||||
assert "id" in data
|
assert "id" in data
|
||||||
|
|
@ -12,7 +12,7 @@ def test_create_snapshot_minimal(client):
|
||||||
|
|
||||||
def test_create_snapshot_full(client):
|
def test_create_snapshot_full(client):
|
||||||
r = client.post(
|
r = client.post(
|
||||||
"/skin-snapshots/",
|
"/skincare",
|
||||||
json={
|
json={
|
||||||
"snapshot_date": "2026-02-20",
|
"snapshot_date": "2026-02-20",
|
||||||
"overall_state": "good",
|
"overall_state": "good",
|
||||||
|
|
@ -39,22 +39,22 @@ def test_create_snapshot_full(client):
|
||||||
|
|
||||||
def test_create_snapshot_invalid_state(client):
|
def test_create_snapshot_invalid_state(client):
|
||||||
r = client.post(
|
r = client.post(
|
||||||
"/skin-snapshots/",
|
"/skincare",
|
||||||
json={"snapshot_date": "2026-02-26", "overall_state": "stellar"},
|
json={"snapshot_date": "2026-02-26", "overall_state": "stellar"},
|
||||||
)
|
)
|
||||||
assert r.status_code == 422
|
assert r.status_code == 422
|
||||||
|
|
||||||
|
|
||||||
def test_list_snapshots_empty(client):
|
def test_list_snapshots_empty(client):
|
||||||
r = client.get("/skin-snapshots/")
|
r = client.get("/skincare")
|
||||||
assert r.status_code == 200
|
assert r.status_code == 200
|
||||||
assert r.json() == []
|
assert r.json() == []
|
||||||
|
|
||||||
|
|
||||||
def test_list_filter_date_range(client):
|
def test_list_filter_date_range(client):
|
||||||
client.post("/skin-snapshots/", json={"snapshot_date": "2026-01-01"})
|
client.post("/skincare", json={"snapshot_date": "2026-01-01"})
|
||||||
client.post("/skin-snapshots/", json={"snapshot_date": "2026-06-01"})
|
client.post("/skincare", json={"snapshot_date": "2026-06-01"})
|
||||||
r = client.get("/skin-snapshots/?from_date=2026-05-01&to_date=2026-12-31")
|
r = client.get("/skincare?from_date=2026-05-01&to_date=2026-12-31")
|
||||||
assert r.status_code == 200
|
assert r.status_code == 200
|
||||||
data = r.json()
|
data = r.json()
|
||||||
assert len(data) == 1
|
assert len(data) == 1
|
||||||
|
|
@ -63,14 +63,14 @@ def test_list_filter_date_range(client):
|
||||||
|
|
||||||
def test_list_filter_overall_state(client):
|
def test_list_filter_overall_state(client):
|
||||||
client.post(
|
client.post(
|
||||||
"/skin-snapshots/",
|
"/skincare",
|
||||||
json={"snapshot_date": "2026-02-10", "overall_state": "good"},
|
json={"snapshot_date": "2026-02-10", "overall_state": "good"},
|
||||||
)
|
)
|
||||||
client.post(
|
client.post(
|
||||||
"/skin-snapshots/",
|
"/skincare",
|
||||||
json={"snapshot_date": "2026-02-11", "overall_state": "poor"},
|
json={"snapshot_date": "2026-02-11", "overall_state": "poor"},
|
||||||
)
|
)
|
||||||
r = client.get("/skin-snapshots/?overall_state=poor")
|
r = client.get("/skincare?overall_state=poor")
|
||||||
assert r.status_code == 200
|
assert r.status_code == 200
|
||||||
data = r.json()
|
data = r.json()
|
||||||
assert len(data) == 1
|
assert len(data) == 1
|
||||||
|
|
@ -78,31 +78,31 @@ def test_list_filter_overall_state(client):
|
||||||
|
|
||||||
|
|
||||||
def test_get_snapshot(client):
|
def test_get_snapshot(client):
|
||||||
r = client.post("/skin-snapshots/", json={"snapshot_date": "2026-02-26"})
|
r = client.post("/skincare", json={"snapshot_date": "2026-02-26"})
|
||||||
sid = r.json()["id"]
|
sid = r.json()["id"]
|
||||||
r2 = client.get(f"/skin-snapshots/{sid}")
|
r2 = client.get(f"/skincare/{sid}")
|
||||||
assert r2.status_code == 200
|
assert r2.status_code == 200
|
||||||
assert r2.json()["id"] == sid
|
assert r2.json()["id"] == sid
|
||||||
|
|
||||||
|
|
||||||
def test_get_snapshot_not_found(client):
|
def test_get_snapshot_not_found(client):
|
||||||
r = client.get(f"/skin-snapshots/{uuid.uuid4()}")
|
r = client.get(f"/skincare/{uuid.uuid4()}")
|
||||||
assert r.status_code == 404
|
assert r.status_code == 404
|
||||||
|
|
||||||
|
|
||||||
def test_update_snapshot_state(client):
|
def test_update_snapshot_state(client):
|
||||||
r = client.post("/skin-snapshots/", json={"snapshot_date": "2026-02-26"})
|
r = client.post("/skincare", json={"snapshot_date": "2026-02-26"})
|
||||||
sid = r.json()["id"]
|
sid = r.json()["id"]
|
||||||
r2 = client.patch(f"/skin-snapshots/{sid}", json={"overall_state": "excellent"})
|
r2 = client.patch(f"/skincare/{sid}", json={"overall_state": "excellent"})
|
||||||
assert r2.status_code == 200
|
assert r2.status_code == 200
|
||||||
assert r2.json()["overall_state"] == "excellent"
|
assert r2.json()["overall_state"] == "excellent"
|
||||||
|
|
||||||
|
|
||||||
def test_update_snapshot_concerns(client):
|
def test_update_snapshot_concerns(client):
|
||||||
r = client.post("/skin-snapshots/", json={"snapshot_date": "2026-02-26"})
|
r = client.post("/skincare", json={"snapshot_date": "2026-02-26"})
|
||||||
sid = r.json()["id"]
|
sid = r.json()["id"]
|
||||||
r2 = client.patch(
|
r2 = client.patch(
|
||||||
f"/skin-snapshots/{sid}",
|
f"/skincare/{sid}",
|
||||||
json={"active_concerns": ["dehydration", "redness"]},
|
json={"active_concerns": ["dehydration", "redness"]},
|
||||||
)
|
)
|
||||||
assert r2.status_code == 200
|
assert r2.status_code == 200
|
||||||
|
|
@ -113,20 +113,20 @@ def test_update_snapshot_concerns(client):
|
||||||
|
|
||||||
def test_update_snapshot_not_found(client):
|
def test_update_snapshot_not_found(client):
|
||||||
r = client.patch(
|
r = client.patch(
|
||||||
f"/skin-snapshots/{uuid.uuid4()}", json={"overall_state": "good"}
|
f"/skincare/{uuid.uuid4()}", json={"overall_state": "good"}
|
||||||
)
|
)
|
||||||
assert r.status_code == 404
|
assert r.status_code == 404
|
||||||
|
|
||||||
|
|
||||||
def test_delete_snapshot(client):
|
def test_delete_snapshot(client):
|
||||||
r = client.post("/skin-snapshots/", json={"snapshot_date": "2026-02-26"})
|
r = client.post("/skincare", json={"snapshot_date": "2026-02-26"})
|
||||||
sid = r.json()["id"]
|
sid = r.json()["id"]
|
||||||
r2 = client.delete(f"/skin-snapshots/{sid}")
|
r2 = client.delete(f"/skincare/{sid}")
|
||||||
assert r2.status_code == 204
|
assert r2.status_code == 204
|
||||||
r3 = client.get(f"/skin-snapshots/{sid}")
|
r3 = client.get(f"/skincare/{sid}")
|
||||||
assert r3.status_code == 404
|
assert r3.status_code == 404
|
||||||
|
|
||||||
|
|
||||||
def test_delete_snapshot_not_found(client):
|
def test_delete_snapshot_not_found(client):
|
||||||
r = client.delete(f"/skin-snapshots/{uuid.uuid4()}")
|
r = client.delete(f"/skincare/{uuid.uuid4()}")
|
||||||
assert r.status_code == 404
|
assert r.status_code == 404
|
||||||
|
|
|
||||||
|
|
@ -26,8 +26,9 @@ export const actions: Actions = {
|
||||||
.map((t) => t.trim())
|
.map((t) => t.trim())
|
||||||
.filter(Boolean) ?? [];
|
.filter(Boolean) ?? [];
|
||||||
|
|
||||||
|
let product;
|
||||||
try {
|
try {
|
||||||
const product = await createProduct({
|
product = await createProduct({
|
||||||
name,
|
name,
|
||||||
brand,
|
brand,
|
||||||
category,
|
category,
|
||||||
|
|
@ -36,9 +37,9 @@ export const actions: Actions = {
|
||||||
leave_on,
|
leave_on,
|
||||||
targets
|
targets
|
||||||
});
|
});
|
||||||
redirect(303, `/products/${product.id}`);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return fail(500, { error: (e as Error).message });
|
return fail(500, { error: (e as Error).message });
|
||||||
}
|
}
|
||||||
|
redirect(303, `/products/${product.id}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -17,11 +17,12 @@ export const actions: Actions = {
|
||||||
return fail(400, { error: 'Date and AM/PM are required' });
|
return fail(400, { error: 'Date and AM/PM are required' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let routine;
|
||||||
try {
|
try {
|
||||||
const routine = await createRoutine({ routine_date, part_of_day, notes: notes || undefined });
|
routine = await createRoutine({ routine_date, part_of_day, notes: notes || undefined });
|
||||||
redirect(303, `/routines/${routine.id}`);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return fail(500, { error: (e as Error).message });
|
return fail(500, { error: (e as Error).message });
|
||||||
}
|
}
|
||||||
|
redirect(303, `/routines/${routine.id}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue