167 lines
7.7 KiB
Markdown
167 lines
7.7 KiB
Markdown
# ADMIN BULK UPDATE — TESTS.md
|
|
|
|
## Unit Tests
|
|
|
|
### Price Handlers
|
|
|
|
```
|
|
BULK-UT-001: INCREASE_PERCENT 5% on 350 cents → proposed = 368 cents
|
|
BULK-UT-002: INCREASE_PERCENT 100% on 200 cents → proposed = 400 cents
|
|
BULK-UT-003: DECREASE_PERCENT 5% on 350 cents → proposed = 333 cents
|
|
BULK-UT-004: DECREASE_PERCENT 50% on 200 cents → proposed = 100 cents
|
|
BULK-UT-005: DECREASE_FIXED 50 on 350 cents → proposed = 300 cents
|
|
BULK-UT-006: INCREASE_FIXED 50 on 350 cents → proposed = 400 cents
|
|
BULK-UT-007: DECREASE_FIXED 500 on 350 cents → proposed = 0 (floor at 0)
|
|
BULK-UT-008: SET_PRICE 500 → proposed = 500
|
|
BULK-UT-009: INCREASE_PERCENT with percent=0 → ValidationResult.valid=false
|
|
BULK-UT-010: INCREASE_PERCENT with percent=-5 → ValidationResult.valid=false
|
|
BULK-UT-011: INCREASE_PERCENT with percent=1001 → ValidationResult.valid=false
|
|
BULK-UT-012: INCREASE_PERCENT without vatRate → ValidationResult.valid=false
|
|
BULK-UT-013: DECREASE_PERCENT with vatRate=general → validation passes
|
|
BULK-UT-014: preview() for product without price → status=failed, error=PRICE_NOT_FOUND
|
|
BULK-UT-015: preview() for product with price → status=valid, proposedPriceCents calculated
|
|
BULK-UT-016: buildPatch() returns correct { pricing: { netUnitAmountCents, vatRate } }
|
|
```
|
|
|
|
### Category Handlers
|
|
|
|
```
|
|
BULK-UT-020: ADD_CATEGORY to product with [A, B] → proposedCategoryIds = [A, B, C] (C added)
|
|
BULK-UT-021: ADD_CATEGORY where C already present → no duplicate (deduplicated)
|
|
BULK-UT-022: REMOVE_CATEGORY from product with [A, B, C] → [A, B]
|
|
BULK-UT-023: REMOVE_CATEGORY where not present → no change
|
|
BULK-UT-024: REPLACE_CATEGORIES with [X, Y] → proposedCategoryIds = [X, Y]
|
|
BULK-UT-025: REPLACE_CATEGORIES with empty array → proposedCategoryIds = []
|
|
BULK-UT-026: ADD_CATEGORY without categoryId → ValidationResult.valid=false
|
|
BULK-UT-027: REMOVE_CATEGORY without categoryId → ValidationResult.valid=false
|
|
BULK-UT-028: buildPatch() returns correct { categoryIds: [...] }
|
|
```
|
|
|
|
### BulkService
|
|
|
|
```
|
|
BULK-UT-030: preview() returns items for all provided productIds
|
|
BULK-UT-031: preview() returns summary with correct valid/failed counts
|
|
BULK-UT-032: preview() throws AppError(400) for unknown operation type
|
|
BULK-UT-033: preview() throws AppError(422) for invalid parameters
|
|
BULK-UT-034: execute() returns operationId
|
|
BULK-UT-035: execute() returns COMPLETED when all succeed
|
|
BULK-UT-036: execute() returns COMPLETED_WITH_ERRORS when some fail
|
|
BULK-UT-037: execute() calls audit.log() with correct metadata
|
|
BULK-UT-038: execute() rollback on error → no product modified (verify by checking DB)
|
|
```
|
|
|
|
## Integration Tests
|
|
|
|
### Database
|
|
|
|
```
|
|
BULK-IT-001: catalog_products.updated_at updated after bulk price change
|
|
BULK-IT-002: pricing_variants.net_unit_amount_cents updated after SET_PRICE
|
|
BULK-IT-003: catalog_product_categories updated correctly for ADD_CATEGORY
|
|
BULK-IT-004: catalog_product_categories updated correctly for REMOVE_CATEGORY
|
|
BULK-IT-005: catalog_product_categories updated correctly for REPLACE_CATEGORIES
|
|
BULK-IT-006: Concurrent bulk operations on overlapping product sets → no race condition
|
|
BULK-IT-007: Price changed by bulk update → subsequent checkout uses new price
|
|
```
|
|
|
|
### API Routes
|
|
|
|
```
|
|
BULK-IT-010: POST /admin/bulk/preview with valid INCREASE_PERCENT → 200 + PreviewResult
|
|
BULK-IT-011: POST /admin/bulk/preview with DECREASE_PERCENT → 200 + PreviewResult
|
|
BULK-IT-012: POST /admin/bulk/preview with ADD_CATEGORY → 200 + PreviewResult
|
|
BULK-IT-013: POST /admin/bulk/preview with REMOVE_CATEGORY → 200 + PreviewResult
|
|
BULK-IT-014: POST /admin/bulk/preview with REPLACE_CATEGORIES → 200 + PreviewResult
|
|
BULK-IT-015: POST /admin/bulk/preview with invalid percent → 422 + error message
|
|
BULK-IT-016: POST /admin/bulk/preview without session cookie → 401
|
|
BULK-IT-017: POST /admin/bulk/preview with customer role → 403
|
|
|
|
BULK-IT-020: POST /admin/bulk/execute with valid SET_PRICE → 200 + ExecuteResult + DB updated
|
|
BULK-IT-021: POST /admin/bulk/execute with valid INCREASE_PERCENT → 200 + DB updated
|
|
BULK-IT-022: POST /admin/bulk/execute with valid ADD_CATEGORY → 200 + DB updated
|
|
BULK-IT-023: POST /admin/bulk/execute → security_audit_log row created
|
|
BULK-IT-024: POST /admin/bulk/execute without auth → 401
|
|
BULK-IT-025: POST /admin/bulk/execute with customer role → 403
|
|
BULK-IT-026: POST /admin/bulk/execute with partial failures → 200 + COMPLETED_WITH_ERRORS
|
|
```
|
|
|
|
## Admin UI Tests
|
|
|
|
```
|
|
BULK-ADM-UT-001: Page loads with product table and checkboxes
|
|
BULK-ADM-UT-002: Selecting 3 products updates "3 products selected" counter
|
|
BULK-ADM-UT-003: Deselecting all shows "0 products selected"
|
|
BULK-ADM-UT-004: Search filter narrows product list
|
|
BULK-ADM-UT-005: "Continue" button disabled when 0 products selected
|
|
|
|
BULK-ADM-UT-010: Clicking Price card shows SET, +%, -%, +FIXED, -FIXED options
|
|
BULK-ADM-UT-011: Clicking Categories card shows ADD, REMOVE, REPLACE options
|
|
BULK-ADM-UT-012: INCREASE_PERCENT selected → percentage input appears
|
|
BULK-ADM-UT-013: INCREASE_PERCENT with invalid input → error state on field
|
|
BULK-ADM-UT-014: ADD_CATEGORY selected → category picker appears
|
|
|
|
BULK-ADM-UT-020: Preview shows product rows with current → proposed values
|
|
BULK-ADM-UT-021: Failed rows highlighted in red with error reason
|
|
BULK-ADM-UT-022: Summary bar shows "X valid, Y failed"
|
|
BULK-ADM-UT-023: "Back to Configure" navigates back
|
|
BULK-ADM-UT-024: Preview does NOT show [Apply Changes] button
|
|
|
|
BULK-ADM-UT-030: Confirmation dialog shows warning text with correct count
|
|
BULK-ADM-UT-031: "Cancel" returns to preview without calling execute
|
|
BULK-ADM-UT-032: "Apply Changes" calls execute API once
|
|
BULK-ADM-UT-033: Result shows success count and failure count
|
|
BULK-ADM-UT-034: "New Bulk Update" button resets wizard to step 1
|
|
```
|
|
|
|
## Given/When/Then Acceptance Criteria
|
|
|
|
```
|
|
GIVEN 20 selected products with valid prices
|
|
WHEN operator previews a 5% price increase
|
|
THEN no product is modified
|
|
AND all 20 proposed prices are calculated by the backend
|
|
AND the preview shows current and proposed values
|
|
|
|
GIVEN a product without a price variant
|
|
WHEN operator previews any price operation on that product
|
|
THEN the preview item has status=failed and error=PRICE_NOT_FOUND
|
|
AND the summary shows failed=1
|
|
|
|
GIVEN 20 selected products
|
|
WHEN operator clicks "Apply Changes" on the confirmation step
|
|
THEN the backend applies the changes in a single transaction
|
|
AND the security_audit_log receives one entry for the bulk operation
|
|
AND each affected product's updated_at is updated
|
|
AND the result shows successful=20, failed=0
|
|
|
|
GIVEN a bulk execute with 2 products invalid and 18 valid
|
|
WHEN the operation runs
|
|
THEN the 18 valid products are updated
|
|
AND the 2 invalid products are not modified
|
|
AND the result shows COMPLETED_WITH_ERRORS with failure details
|
|
AND the audit log entry captures the partial failure
|
|
|
|
GIVEN a non-admin user
|
|
WHEN they call POST /admin/bulk/execute
|
|
THEN the backend returns 403 Forbidden
|
|
AND no audit entry is created
|
|
AND no product is modified
|
|
|
|
GIVEN a preview shows 18 valid changes
|
|
WHEN a second operator applies a price change to one of those products before execute
|
|
THEN the execute still applies the original preview's proposed price
|
|
AND no optimistic concurrency check is silently bypassed (document this behavior)
|
|
```
|
|
|
|
---
|
|
|
|
## Preview Staleness Policy
|
|
|
|
Preview data may be stale when execute runs (another admin changed a price). Current design: execute uses the same calculation on current DB state, which is correct behavior for bulk updates (we want to apply the percentage to whatever the current price is, not the stale preview price).
|
|
|
|
If stricter concurrency control is needed (e.g., "apply only if price hasn't changed since preview"), this requires:
|
|
- `expected_version` or `updated_at` in preview response
|
|
- Compare at execute time
|
|
- This is a future enhancement, not MVP scope.
|