Scope: FastAPI backend under app/ including tenant middleware, public/admin routers, and internal management APIs. Covers endpoints mounted in app/app.py with prefixes:
- Public:
/tenant/{tenant_id}/... - Admin:
/admin/tenant/{tenant_id}/... - Internal:
/internal/...
Assumptions
- Multi-tenant SQLite by default; tenants must exist (or autoload from disk) or requests return 404 via
TenantMiddleware. - Admin and public routers expose the same domain endpoints; no auth guards on admin routes in current code.
General
- CORS:
OPTIONS /*returns 200 JSON {"message":"OK"}. - Root:
GET /returns service/version. - Health:
GET /healthreturns aggregated health. - OpenAPI docs:
GET /docs,GET /docs/admin,GET /openapi-public.json,GET /openapi-admin.jsonrender and filter routes appropriately.
Tenant Middleware (app/middleware/tenant.py)
- Valid paths required:
/tenant/{tenant_id}/*or/admin/tenant/{tenant_id}/*or/internal/*.- Invalid path shape → 400 {error: Invalid URL format}.
- Invalid tenant id (regex/length) → 400.
- Unregistered tenant without on-disk DB → 404 {error: Tenant database not found}.
- Inactive tenant → 403.
- When registered or auto-loaded from disk → request proceeds;
request.state.tenant_idset.
Categories API (/{tenant_id}/categories)
- List:
GET /{tenant_id}/categories- Happy path: default order by
sort_order; meta: total, offset, limit, count. - Filtering operators: eq, neq, lt, lte, gt, gte, like, ilike, in, not.in, is.null, not.is.null.
- Ordering:
order=name.asc,created_at.desc. - Pagination:
limit,offset. - Field selection param
selectis accepted but not applied to serialization (validate no error). - Error: invalid numeric casts are tolerated (treated as strings), still 200.
- Happy path: default order by
- Tree:
GET /{tenant_id}/categories/tree- Returns hierarchical
categoriesarray withchildrenarrays; optionalis_activefilter. - Timestamps formatted iso8601.
- Returns hierarchical
- Create:
POST /{tenant_id}/categories- Happy path with unique
slug; optionalparent_idmust exist. - Errors: duplicate slug → 400; parent not found → 400.
- Happy path with unique
- Retrieve:
GET /{tenant_id}/categories/{id}- 200 on exist, 404 if not.
- Update (PUT):
PUT /{tenant_id}/categories/{id}- Replaces all fields; duplicate slug (excluding self) → 400.
- Parent validation: cannot be self; parent must exist; circular detection via parent chain → 400.
- 404 if not found.
- Patch:
PATCH /{tenant_id}/categories/{id}- Partial fields only; duplicate slug (excluding self) → 400; parent checks as above.
- Delete:
DELETE /{tenant_id}/categories/{id}- Happy path: 204.
- Errors: has children → 400; has products referencing category → 400; not found → 404.
Products API (/{tenant_id}/products)
- List:
GET /{tenant_id}/products- Supports same operator suite as categories across defined fields; returns
{data, meta}; joined category serialization when present. - Verify ordering parsing and multiple fields.
- Verify count meta accuracy with pagination.
- Supports same operator suite as categories across defined fields; returns
- Create (multipart):
POST /{tenant_id}/products- Form parts:
product_dataJSON (required),images[],videos[],other_media[]. - Validations: unique
sku, uniqueslug; numeric/enum coercions; decimals serialized to float. - Media records created with
media_typeimage|video|other;sort_orderincrements. - Errors: missing/invalid
product_dataJSON → 400; duplicate sku/slug → 400.
- Form parts:
- Retrieve:
GET /{tenant_id}/products/{product_id}- 200 with category info if linked; 404 if not found.
- Update (multipart, full):
PUT /{tenant_id}/products/{product_id}- Validates slug/sku uniqueness excluding self.
replace_media(bool) defaults true; when true and new files provided, deletes existing media; else appends at next sort.- 404 when missing.
- Patch (multipart, partial):
PATCH /{tenant_id}/products/{product_id}product_dataoptional; applies provided fields only; uniqueness checks when fields present.replace_mediadefault false; behavior like PUT for media.
- Delete:
DELETE /{tenant_id}/products/{product_id}→ 204 or 404. - Images list:
GET /{tenant_id}/products/{product_id}/images→ 200 or 404 when product missing. - Add image:
POST /{tenant_id}/products/{product_id}/imageswithfile→ 201 creates placeholder URL; 404 when product missing. - Unified convenience endpoints:
POST /{tenant_id}/products/with-images→ create product + images; duplicate SKU → 400; required name/sku/slug.PUT /{tenant_id}/products/{product_id}/with-images→ replace images if provided; not found → 404.
Orders API (/{tenant_id}/orders)
- List:
GET /{tenant_id}/orders- Filters:
status,payment_status,fulfillment_status(eq.),order_number(ilike.), order by created_at (asc|desc), pagination vialimit/offset. - Response is array (not wrapped), numeric totals serialized floats.
- Filters:
- Create:
POST /{tenant_id}/orders- Requires existing
customer_id;items[]each withproduct_id,quantity>=1,unit_price>=0. - Inventory handling: if
track_inventoryand insufficient and notcontinue_selling_when_out_of_stock→ 400. - Generates order_number if blank; sets totals.
- Errors: missing customer → 400; missing product → 400.
- Requires existing
- Retrieve:
GET /{tenant_id}/orders/{order_id}→ 200 or 404. - Patch:
PATCH /{tenant_id}/orders/{order_id}partial update; validatecustomer_idif changed; 404 when missing. - Delete:
DELETE /{tenant_id}/orders/{order_id}- Not allowed when status in confirmed|shipped|delivered → 400.
- Restores inventory for canceled order items; 404 when missing.
- Order items:
- List:
GET /{tenant_id}/orders/{order_id}/items→ 200 or 404 when order missing. - Add:
POST /{tenant_id}/orders/{order_id}/items→ validates order status (pending|confirmed), product existence, inventory; updates order totals and product inventory. - Retrieve:
GET /{tenant_id}/orders/{order_id}/items/{item_id}→ 200 or 404.
- List:
Authentication API (/{tenant_id}/auth/*)
- Register:
POST /{tenant_id}/auth/register- Validates username/email uniqueness, email format, password length ≥ 6; creates user with default role SELLER.
- Duplicate username/email → 400.
- Login:
POST /{tenant_id}/auth/login- Accepts username OR email; invalid credentials or inactive user → 401.
- Creates active
Sessionwith token/refresh_token; trims to max 5 active sessions per user.
- Logout:
POST /{tenant_id}/auth/logout- Requires
Authorization: Bearer <token>; revokes session if found; 401 invalid format.
- Requires
- Me:
GET /{tenant_id}/auth/me- Requires Bearer token; session must be active and not expired; 401 invalid/expired.
- Refresh:
POST /{tenant_id}/auth/refresh?refresh_token=...- Requires valid active session by refresh token; rotates tokens; 401 invalid/expired.
- Change password:
POST /{tenant_id}/auth/change-password?current_password=...&new_password=...- Requires Bearer; validates current password;
new_passwordlength; returns success; 401/400 on errors.
- Requires Bearer; validates current password;
- Sessions list:
GET /{tenant_id}/auth/sessions- Requires Bearer; returns sanitized sessions, marks
is_current.
- Requires Bearer; returns sanitized sessions, marks
- Revoke session:
DELETE /{tenant_id}/auth/sessions/{session_id}- Requires Bearer; can only revoke own session; 404 if not found.
Internal Management API (/internal/database/*)
- Create DB:
POST /internal/database/?environment=development|productiondatabaseIdbody required (pattern/length); returnscreatedtrue on first creation, false if exists; dev runs migrations best-effort.- Note: bearer verification currently commented out; no auth required in code.
- Delete DB:
DELETE /internal/database/{database_id}- Deletes SQLite file (if exists), deactivates tenant, cleans resources; returns deleted true/false.
- Status:
GET /internal/database/{database_id}/status?environment=development|production- Validates tenant id; dev may auto-load in registry; returns config and migration status (best-effort).
- List configs:
GET /internal/database/configs?status=&environment=- Returns all tenant configs with runtime info for active dev tenants.
Negative/Edge Case Matrix (Representative)
- Tenant ID edge cases: empty, too long (>50), invalid chars, valid but missing DB.
- Query operators: malformed (e.g.,
in.(),not.in.()), unknown fields ignored, invalid date formats handled as strings. - Product create/update: boundary values (price=0, inventory=0), large texts at max lengths, many media files (verify sort order),
replace_mediabehavior when no files provided. - Orders: zero items (should fail via Pydantic min_items), large quantities and precision, mixed track_inventory scenarios.
- Auth: invalid/missing Bearer header, expired session (simulate via model method if possible), multiple concurrent sessions revocation order.
- Internal: attempt to create prod without POSTGRES provisioning (expect config created, no DB provisioning), delete nonexistent DB, status for nonexistent environment.
Nonexistent/Missing Endpoints (to acknowledge)
- No
/tenant/{tenant_id}/uploadendpoint (frontend calls this; expect 404). - No
/tenant/{tenant_id}/dashboard/stats,/tenant/{tenant_id}/customers/*(admin set), ororders/{id}/summaryendpoints in backend.
Test Data & Setup
- Use
/internal/databaseto create a dev tenant before tests, or pre-seed with provided setup scripts (e.g.,setup_demo.py). - Use deterministic tenant IDs per test run to avoid collisions (e.g.,
user0001_project0001).
- Basic endpoints: Root, Health check
- Tenant middleware: Nonexistent tenant (404), Invalid path (400)
- Categories API: List, Tree, Filtering, Ordering, Pagination
- Products API: List, Filtering, Search
- Orders API: List, Filtering
- Auth API: Login, Get current user
- Edge cases: Duplicate category slug, Invalid IDs
- Internal API: List database configs
- CORS OPTIONS - Error in test script (variable reference issue)
- Category Creation - Returns 201 instead of expected 200 (this is actually correct behavior)
- Product Creation with Media - 422 error: multipart file handling issue in test
- Order Creation - 422 error: product_id is None due to failed product creation
- Database Status - 500 error: internal API issue
- Orders API - Fixed SQLAlchemy enum handling for order statuses
- Product Creation - Fixed multipart file upload (works without media files)
- React State Management - Fixed infinite re-render loop in ProductFormPage
- Fix multipart file upload test implementation
- Fix internal database status endpoint error
- Update test expectations for 201 status codes (they're correct)
- Fix CORS OPTIONS test implementation