Test Cases: Product
| Module | CORE-05 | URD | Product URD |
|---|
Scenarios use Given / When / Then.
TC-<AREA>-NNNlines up withURD-<AREA>-NNN. Priority = P1 (critical) / P2 (major) / P3 (minor).
1. Coverage Summary
| Area | URD reqs | Test cases | Covered |
|---|---|---|---|
Product (PRD) | 16 | 31 | ✅ |
Category (CAT) | 4 | 8 | ✅ |
Variant (VAR) | 12 | 24 | ✅ |
Fare / Pricing (FAR) | 7 | 17 | ✅ |
Identifiers (PID) | 4 | 8 | ✅ |
Promotions (CMP) | 3 | 5 | ⚠️ (calc Planned) |
Access (ACC) | 4 | 11 | ✅ |
Constraints (CON) | 7 | 13 | ✅ |
The variant pricing-read endpoint (
URD-VAR-011), CSV import (URD-PRD-019), and promotion discount calculation (URD-CMP-003) are Planned / In-progress; their test cases verify the current behavior (error / not-applied), not full functionality.
2. Test Cases
2.1 Product (PRD)
| TC ID | URD ref | Scenario | Steps | Expected | P |
|---|---|---|---|---|---|
| TC-PRD-001 | URD-PRD-001, -002, -004 | Create product (happy path) | Owner creates product with name + category | Created; SYSTEM id + slug generated; default variant created; status Activated | P1 |
| TC-PRD-002 | URD-PRD-001 | Create without name | Submit with no name | Rejected; name required | P1 |
| TC-PRD-003 | URD-PRD-001 | Create without category | Submit with no category | Rejected; category required | P1 |
| TC-PRD-004 | URD-PRD-003 | Multilingual name | Provide name in EN + VI | All languages stored & retrievable | P1 |
| TC-PRD-005 | URD-PRD-003 | Partial language | Primary name only, others empty | Created; empty languages stored as empty (no error) | P2 |
| TC-PRD-006 | URD-PRD-005 | Update info | Owner updates name & description | Updated; variants/fares unchanged | P1 |
| TC-PRD-007 | URD-PRD-005 | Update to empty name | Owner clears the name | Rejected; original preserved | P1 |
| TC-PRD-008 | URD-PRD-006 | Deactivate | Owner deactivates an active product | Status Deactivated; data preserved; hidden from POS | P1 |
| TC-PRD-009 | URD-PRD-006 | View deactivated | Owner opens a deactivated product | All data intact; status shows Deactivated | P2 |
| TC-PRD-010 | URD-PRD-007 | Slug collision same merchant | Create a product that would reuse an existing slug | System ensures uniqueness (distinct slugs) | P1 |
| TC-PRD-011 | URD-PRD-007 | Slug across merchants | Two merchants both create "coffee-latte" | Both succeed; slug unique per merchant, may match across merchants | P2 |
| TC-PRD-012 | URD-PRD-008 | Lookup by ID | Search by known ID | Product returned with name, category, status, variants | P1 |
| TC-PRD-013 | URD-PRD-008 | Lookup by slug | Search by known slug | Product returned | P1 |
| TC-PRD-014 | URD-PRD-008 | Lookup missing ID | Search by non-existent ID | Not found; nothing returned | P2 |
| TC-PRD-015 | URD-PRD-009 | List (role-filtered) | Owner requests product list | Only products under own merchants returned | P1 |
| TC-PRD-016 | URD-PRD-009 | Count consistency | Owner requests product count | Count respects the same role filter as the list | P2 |
| TC-PRD-017 | URD-PRD-013 | Attach image | Owner attaches images | Images linked & retrievable | P2 |
| TC-PRD-018 | URD-PRD-013 | Bad image format | Owner uploads unsupported format | Rejected; allowed formats indicated | P3 |
| TC-PRD-019 | URD-PRD-014 | Assign sale channels | Owner assigns product to specific channels | Available only in assigned channels | P2 |
| TC-PRD-020 | URD-PRD-014 | Remove from channel | Remove product from channel B | Stays in A; gone from B | P2 |
| TC-PRD-021 | URD-PRD-016 | Parent-child | Create a child product under a parent | Child references parent; parent lists children | P2 |
| TC-PRD-023 | URD-PRD-006 | Archive | Owner archives a product | Status Archived; data preserved; hidden from POS & list | P1 |
| TC-PRD-024 | URD-PRD-008 | Lookup missing slug | Search by non-existent slug | Not found; nothing returned | P2 |
| TC-PRD-025 | URD-PRD-006 | Reactivate | Owner reactivates a deactivated product | Status Activated; visible again; data unchanged | P1 |
| TC-PRD-026 | URD-PRD-001 | Create without merchant | Owner creates without merchant context | Rejected; merchant required | P1 |
| TC-PRD-027 | URD-PRD-007 | Empty-string slug | Owner submits "" as slug | System auto-generates a valid slug; product findable | P2 |
| TC-PRD-028 | URD-PRD-014 | Channel diff update | Update channels [A,B,C] → [B,D] | A & C removed, D added, B kept — atomically | P2 |
| TC-PRD-029 | URD-PRD-005, -014 | Channel-only update | Aggregate update with no product change, new channels | Product unchanged; channels updated; no redundant product write | P2 |
| TC-PRD-030 | URD-PRD-017 | Search by name | Search "Cafe" across products | Case-insensitive partial matches returned | P1 |
| TC-PRD-031 | URD-PRD-017 | Diacritics search | Search "ca phe" against diacritic name | Returned if i18n search normalizes diacritics; behavior documented | P2 |
| TC-PRD-032 | URD-PRD-018 | Filter by category | Filter list by "Beverages" | Only Beverages products returned | P1 |
| TC-PRD-033 | URD-PRD-015 | Archived is read-only | Update an archived product | Rejected; archived is read-only | P1 |
| TC-PRD-034 | URD-PRD-015 | Archive is terminal | Reactivate an archived product | Rejected; Archived is terminal | P1 |
| TC-PRD-022 | URD-PRD-019 | CSV import (Planned) | Upload a catalogue file | Feature Planned — test expanded when implemented | P3 |
2.2 Category (CAT)
| TC ID | URD ref | Scenario | Steps | Expected | P |
|---|---|---|---|---|---|
| TC-CAT-001 | URD-CAT-001 | Create category | Owner creates a category with a name | Created within merchant; available for assignment | P1 |
| TC-CAT-002 | URD-CAT-001 | Create without name | Submit with no name | Rejected; name required | P1 |
| TC-CAT-003 | URD-CAT-002 | Multilingual name | Provide name in multiple languages | All languages stored & retrievable | P2 |
| TC-CAT-004 | URD-CAT-003 | Update category | Owner updates the name | Updated; products unaffected | P2 |
| TC-CAT-005 | URD-CAT-003 | Delete empty category | Delete a category with no products | Soft-deleted; gone from list | P2 |
| TC-CAT-006 | URD-CAT-003 | Delete in-use category | Delete a category with products | Rejected; category in use; products keep assignment | P1 |
| TC-CAT-007 | URD-CAT-004 | Flag add-on | Mark category as add-on | Flagged; products treated as add-on | P2 |
| TC-CAT-008 | URD-CAT-004 | Unflag add-on | Remove add-on flag | Reverts to standard category behavior | P3 |
2.3 Variant (VAR)
| TC ID | URD ref | Scenario | Steps | Expected | P |
|---|---|---|---|---|---|
| TC-VAR-001 | URD-VAR-001 | Default variant exists | Create a product | Default variant exists with a system identifier | P1 |
| TC-VAR-002 | URD-VAR-001 | Cannot delete last variant | Delete the only variant | Rejected; at least one variant always remains | P1 |
| TC-VAR-003 | URD-VAR-002 | Add variant | Create an additional "Large" variant | Linked; product now has two variants | P1 |
| TC-VAR-004 | URD-VAR-003, -005 | Aggregate create | Create variant + info + fares + identifiers | All created atomically; identifiers generated | P1 |
| TC-VAR-005 | URD-VAR-003 | Aggregate create rollback | Aggregate create with a negative fare | Whole operation rejected; nothing created | P1 |
| TC-VAR-006 | URD-VAR-004 | Aggregate update | Update name, fare, identifiers in one step | All updated atomically | P1 |
| TC-VAR-007 | URD-VAR-004 | Aggregate update rollback | Valid name but invalid fare | Whole update rejected; originals preserved | P1 |
| TC-VAR-008 | URD-VAR-005 | System identifier | Create a variant | Unique system identifier; not user-editable | P2 |
| TC-VAR-009 | URD-VAR-007 | Effective range | Set from/to dates | Available only inside the window | P2 |
| TC-VAR-010 | URD-VAR-007 | Expired range | End date in the past, add to cart | Not available for sale | P2 |
| TC-VAR-011 | URD-VAR-006 | Set variant type | Set type to SERVICE / KIT / COMBO etc. | Type stored; drives stocking & bundling behavior | P2 |
| TC-VAR-012 | URD-VAR-008 | Assign SKU | Assign an SKU | Linked; searchable by SKU | P2 |
| TC-VAR-013 | URD-VAR-008 | Assign barcode | Assign a barcode | Linked; searchable by barcode | P2 |
| TC-VAR-014 | URD-VAR-001 | Deactivate variant | Deactivate a non-only variant | Status Deactivated; data preserved; hidden from POS | P1 |
| TC-VAR-015 | URD-VAR-001 | Archive variant | Archive a variant | Status Archived; data preserved; hidden from POS & list | P1 |
| TC-VAR-016 | URD-VAR-001 | New status | Create a variant | Status New; not sellable until activated | P2 |
| TC-VAR-017 | URD-VAR-001 | Activate variant | Activate a New variant | Status Activated; sellable | P2 |
| TC-VAR-018 | URD-VAR-001 | Reactivate variant | Reactivate a deactivated variant | Status Activated; data unchanged | P2 |
| TC-VAR-019 | URD-VAR-011 | Pricing read (Planned) | Call variant pricing endpoint | Returns a structured "not implemented" error, not a raw 500 | P2 |
| TC-VAR-020 | URD-VAR-004 | Missing-merchant guard | Aggregate update on a variant lacking merchantId metadata | Rejected (500 by design); transaction rolled back | P2 |
| TC-VAR-021 | URD-VAR-004 | Fare-only update | Update only the fare amount | Name unchanged (no redundant write); fare updated; commits | P2 |
| TC-VAR-022 | URD-VAR-008, URD-PID-004 | First-time SKU on update | Variant without SKU; update assigns SKU | SKU created & linked, OR a clear error — never silently ignored | P2 |
| TC-VAR-023 | URD-VAR-001 | Archived terminal | Transition Archived → Activated | Rejected; Archived is terminal | P1 |
| TC-VAR-024 | URD-VAR-001 | Invalid skip | Transition New → Archived directly | Rejected; not a valid transition | P2 |
2.4 Fare / Pricing (FAR)
| TC ID | URD ref | Scenario | Steps | Expected | P |
|---|---|---|---|---|---|
| TC-FAR-001 | URD-FAR-001 | One fare set | Create a variant | Exactly one fare set linked; cannot be removed | P1 |
| TC-FAR-002 | URD-FAR-001 | No second fare set | Create a second fare set | Rejected; one fare set per variant | P1 |
| TC-FAR-003 | URD-FAR-002 | Base fare present | View a variant's fare set | At least one (default) fare exists | P1 |
| TC-FAR-004 | URD-FAR-002 | Cannot delete last fare | Delete the only fare | Rejected; one fare must always exist | P1 |
| TC-FAR-005 | URD-FAR-003 | Zero amount allowed | Create a fare of amount 0 | Created (zero allowed) | P2 |
| TC-FAR-006 | URD-FAR-003 | Negative rejected | Create a fare of amount -100 | Rejected; amount must be ≥ 0 | P1 |
| TC-FAR-007 | URD-FAR-004 | Base fare applied | Add variant with only a base fare | Base fare applied & displayed | P1 |
| TC-FAR-008 | URD-FAR-004 | Override wins | Variant with base + active override | Override price selected | P1 |
| TC-FAR-009 | URD-FAR-004 | All expired | All fares have expired dates | Falls back to the default fare; no expired price used | P1 |
| TC-FAR-010 | URD-FAR-005 | In-window date | Fare effective Mar 1–31, sale Mar 15 | Fare active & applied | P2 |
| TC-FAR-011 | URD-FAR-005 | Out-of-window date | Same fare, sale Apr 1 | Not applied; next applicable fare used | P2 |
| TC-FAR-012 | URD-FAR-006 | In quantity range | Fare min 10 / max 50, add 20 | Fare applied | P2 |
| TC-FAR-013 | URD-FAR-006 | Below minimum | Fare min 10, add 5 | Not applied; next fare used | P2 |
| TC-FAR-014 | URD-FAR-006 | Above maximum | Fare max 50, add 100 | Not applied; next fare used | P2 |
| TC-FAR-015 | URD-FAR-007 | Fare group hierarchy | Parent fare with children | Parent-child maintained; children count correct | P2 |
| TC-FAR-016 | URD-FAR-004 | Create override | Add an OVERRIDE fare to a set | Override created; prioritized over base during resolution | P1 |
| TC-FAR-017 | URD-FAR-004, -005, -006 | Multi-factor resolution | Base + quantity + seasonal override, add 15 on Mar 15 | Seasonal override selected (override priority); total = 15 × override amount | P1 |
2.5 Identifiers (PID)
| TC ID | URD ref | Scenario | Steps | Expected | P |
|---|---|---|---|---|---|
| TC-PID-001 | URD-PID-001 | Per-scheme uniqueness | Reuse an SKU code on another product | Rejected; identifier already used for that scheme | P1 |
| TC-PID-002 | URD-PID-001 | Same code, different scheme | Same code under SKU and Barcode | Both accepted; uniqueness is per scheme | P2 |
| TC-PID-003 | URD-PID-002 | Multiple identifiers | Assign across SYSTEM, SLUG, SKU, Barcode | All stored; searchable by any | P1 |
| TC-PID-004 | URD-PID-003 | Auto-generated | Create a product / variant | SYSTEM and SLUG auto-generated, unique, not editable | P1 |
| TC-PID-005 | URD-PID-003 | No identifier supplied | Create without supplying an identifier | SYSTEM & SLUG still generated; no error | P2 |
| TC-PID-006 | URD-PID-004 | Assign SKU + barcode | Assign both to a variant | Both linked; searchable by either | P2 |
| TC-PID-007 | URD-PID-001, -004 | Duplicate SKU | Assign an in-use SKU to another variant | Rejected; SKU already in use | P1 |
| TC-PID-008 | URD-PID-004 | Assign QR code | Assign a QR code to a variant | Linked; searchable by QR code | P2 |
2.6 Promotions (CMP)
| TC ID | URD ref | Scenario | Steps | Expected | P |
|---|---|---|---|---|---|
| TC-CMP-001 | URD-CMP-001 | Create promotion | Create with name + date range | Created; linked to merchant | P2 |
| TC-CMP-002 | URD-CMP-001 | No date range | Create without dates | Rejected; date range required | P2 |
| TC-CMP-003 | URD-CMP-001 | Invalid range | End date before start | Rejected; invalid range | P2 |
| TC-CMP-004 | URD-CMP-002 | Merchant scope | View Merchant A's promotion under Merchant B | Not visible under B | P2 |
| TC-CMP-005 | URD-CMP-003 | Discount applied (Planned) | Apply a promotion at pricing | Discount calc disabled today — returns 0; test expanded when engine lands | P3 |
2.7 Access (ACC)
| TC ID | URD ref | Scenario | Steps | Expected | P |
|---|---|---|---|---|---|
| TC-ACC-001 | URD-ACC-001 | List filtered | Any user lists products | Filtered by role; nothing outside scope | P1 |
| TC-ACC-002 | URD-ACC-001 | Count filtered | Any user counts products | Count reflects only in-scope products | P1 |
| TC-ACC-003 | URD-ACC-002 | Owner scope | Owner A lists products | Only A's merchants returned; B's hidden | P1 |
| TC-ACC-004 | URD-ACC-002 | Cross-owner block | Owner A accesses Owner B's product | Denied | P1 |
| TC-ACC-005 | URD-ACC-003 | Employee scope | Employee of X lists products | Only X's products returned | P1 |
| TC-ACC-006 | URD-ACC-003 | Employee block | Employee of X accesses Y's product | Denied | P1 |
| TC-ACC-007 | URD-ACC-004 | Super Admin all | Super Admin lists products | All products returned; no filtering | P1 |
| TC-ACC-008 | URD-ACC-004 | Admin all | Admin lists products | All products; filtering bypassed | P1 |
| TC-ACC-009 | URD-ACC-001 | Category list filtered | Owner lists categories | Only own merchants' categories returned | P1 |
| TC-ACC-010 | URD-PRD-018 | Category filter scope | Filter products by category | Only that category's products returned | P2 |
| TC-ACC-011 | URD-ACC-001 | Unrecognized role | Unhandled role lists products | 403 OR empty with clear insufficient-permission signal — never a silent empty list | P1 |
2.8 Constraints (CON)
| TC ID | URD ref | Scenario | Steps | Expected | P |
|---|---|---|---|---|---|
| TC-CON-001 | C-01 | Single merchant | Assign a product to a second merchant | Rejected | P1 |
| TC-CON-002 | C-01 | Category-merchant match | Assign a foreign-merchant product to a category | Rejected; must share merchant | P1 |
| TC-CON-003 | C-06 | Soft-delete | Search including inactive after delete | Record still retrievable; data preserved | P1 |
| TC-CON-004 | URD-PID-003 | Auto-gen identifiers | Create without SYSTEM/SLUG | Both auto-generated, unique, consistent | P1 |
| TC-CON-005 | C-05 | Aggregate atomic create | Aggregate create with invalid fare | Whole operation fails; nothing created | P1 |
| TC-CON-006 | C-05 | Aggregate atomic update | Valid name + invalid fare on update | Whole update fails; originals preserved | P1 |
| TC-CON-007 | C-07 | Role filter on list | Owner lists products | Filtered before return; count matches | P1 |
| TC-CON-008 | C-07 | Count matches list | Owner with 5 products counts | Returns 5, not the global total | P1 |
| TC-CON-009 | URD-PRD-003 | i18n storage | Create with EN/VI/JA names | All stored; returned per requested locale | P1 |
| TC-CON-010 | C-04 | One fare set | View a new variant's pricing | Exactly one fare set, auto-created | P1 |
| TC-CON-011 | C-04 | One fare minimum | View a fare set | At least one fare; last fare cannot be removed | P1 |
| TC-CON-012 | C-05 | Sub-operation rollback | One sub-step of aggregate product create fails | Whole transaction rolled back; no orphans | P1 |
| TC-CON-013 | C-05 | Caller transaction ownership | A caller passes its own transaction to a lookup | Lookup neither commits nor rolls back; caller keeps control | P2 |
3. Traceability
Every Must requirement maps to ≥1 test case. Planned / In-progress items are flagged.
| URD requirement | Test case(s) | Status |
|---|---|---|
| URD-PRD-001 | TC-PRD-001, -002, -003, -026 | ✅ Covered |
| URD-PRD-002 | TC-PRD-001 | ✅ Covered |
| URD-PRD-003 | TC-PRD-004, -005, TC-CON-009 | ✅ Covered |
| URD-PRD-004 | TC-PRD-001 | ✅ Covered |
| URD-PRD-005 | TC-PRD-006, -007, -029 | ✅ Covered |
| URD-PRD-006 | TC-PRD-008, -009, -023, -025 | ✅ Covered |
| URD-PRD-007 | TC-PRD-010, -011, -027 | ✅ Covered |
| URD-PRD-008 | TC-PRD-012, -013, -014, -024 | ✅ Covered |
| URD-PRD-009 | TC-PRD-015, -016, TC-ACC-009 | ✅ Covered |
| URD-PRD-013 | TC-PRD-017, -018 | ✅ Covered |
| URD-PRD-014 | TC-PRD-019, -020, -028, -029 | ✅ Covered |
| URD-PRD-015 | TC-PRD-033, -034 | ✅ Covered |
| URD-PRD-016 | TC-PRD-021 | ✅ Covered |
| URD-PRD-017 | TC-PRD-030, -031 | ✅ Covered |
| URD-PRD-018 | TC-PRD-032, TC-ACC-010 | ✅ Covered |
| URD-PRD-019 | TC-PRD-022 | ⚠️ Planned (Could) |
| URD-CAT-001 | TC-CAT-001, -002 | ✅ Covered |
| URD-CAT-002 | TC-CAT-003 | ✅ Covered |
| URD-CAT-003 | TC-CAT-004, -005, -006 | ✅ Covered |
| URD-CAT-004 | TC-CAT-007, -008 | ✅ Covered |
| URD-VAR-001 | TC-VAR-001, -002, -014, -015, -016, -017, -018, -023, -024 | ✅ Covered |
| URD-VAR-002 | TC-VAR-003 | ✅ Covered |
| URD-VAR-003 | TC-VAR-004, -005 | ✅ Covered |
| URD-VAR-004 | TC-VAR-006, -007, -020, -021 | ✅ Covered |
| URD-VAR-005 | TC-VAR-004, -008 | ✅ Covered |
| URD-VAR-006 | TC-VAR-011 | ✅ Covered |
| URD-VAR-007 | TC-VAR-009, -010 | ✅ Covered |
| URD-VAR-008 | TC-VAR-012, -013, -022 | ✅ Covered |
| URD-VAR-009 | — | ⚠️ UOM storage built; no dedicated TC yet |
| URD-VAR-010 | — | ⚠️ Bundles built; covered in dev docs, no module TC yet |
| URD-VAR-011 | TC-VAR-019 | ⚠️ Planned (verifies not-implemented error) |
| URD-VAR-012 | — | ⚠️ Planned (no TC) |
| URD-FAR-001 | TC-FAR-001, -002, TC-CON-010 | ✅ Covered |
| URD-FAR-002 | TC-FAR-003, -004, TC-CON-011 | ✅ Covered |
| URD-FAR-003 | TC-FAR-005, -006 | ✅ Covered |
| URD-FAR-004 | TC-FAR-007, -008, -009, -016, -017 | ✅ Covered |
| URD-FAR-005 | TC-FAR-010, -011 | ✅ Covered |
| URD-FAR-006 | TC-FAR-012, -013, -014 | ✅ Covered |
| URD-FAR-007 | TC-FAR-015 | ✅ Covered |
| URD-PID-001 | TC-PID-001, -002, -007 | ✅ Covered |
| URD-PID-002 | TC-PID-003 | ✅ Covered |
| URD-PID-003 | TC-PID-004, -005, TC-CON-004 | ✅ Covered |
| URD-PID-004 | TC-PID-006, -007, -008, TC-VAR-022 | ✅ Covered |
| URD-CMP-001 | TC-CMP-001, -002, -003 | ⚠️ In-progress (CRUD only) |
| URD-CMP-002 | TC-CMP-004 | ⚠️ In-progress |
| URD-CMP-003 | TC-CMP-005 | ⚠️ Planned (calc disabled) |
| URD-ACC-001 | TC-ACC-001, -002, -009, -011 | ✅ Covered |
| URD-ACC-002 | TC-ACC-003, -004 | ✅ Covered |
| URD-ACC-003 | TC-ACC-005, -006 | ✅ Covered |
| URD-ACC-004 | TC-ACC-007, -008 | ✅ Covered |
Summary: all Must requirements covered. Gaps are limited to Planned / In-progress items (URD-PRD-019, URD-VAR-011/012, URD-CMP-003) and two built-but-untested Should items (URD-VAR-009 UOM, URD-VAR-010 bundles) flagged for future test authoring.