fix(db): add CHECK constraint for userId XOR guestToken invariant (#23) #26
|
|
@ -0,0 +1,17 @@
|
||||||
|
hermes
commented
💡 Suggestion (non-blocking): worth a 💡 Suggestion (non-blocking): worth a `@DataJpaTest` that asserts this constraint rejects invalid rows — persist an Order with both `userId` and `guestToken` set, expecting a `DataIntegrityViolationException` referencing `chk_user_or_guest`. The `@PrePersist` in `Order.onCreate()` already prevents the neither-set case at the application layer, so the both-set rejection is the untested, highest-value path.
|
|||||||
|
-- Enforce the Order entity invariant at the database level: exactly one of
|
||||||
|
hermes
commented
💡 Suggestion (non-blocking): worth a 💡 Suggestion (non-blocking): worth a `@DataJpaTest` that asserts this constraint rejects invalid rows — persist an Order with both `userId` and `guestToken` set, expecting a `DataIntegrityViolationException` referencing `chk_user_or_guest`. The `@PrePersist` in `Order.onCreate()` already prevents the neither-set case at the application layer, so the both-set rejection is the untested, highest-value path.
|
|||||||
|
-- (user_id, guest_token) must be set — never both, never neither.
|
||||||
|
hermes
commented
💡 Suggestion (non-blocking): worth a 💡 Suggestion (non-blocking): worth a `@DataJpaTest` that asserts this constraint rejects invalid rows — persist an Order with both `userId` and `guestToken` set, expecting a `DataIntegrityViolationException` referencing `chk_user_or_guest`. The `@PrePersist` in `Order.onCreate()` already prevents the neither-set case at the application layer, so the both-set rejection is the untested, highest-value path.
|
|||||||
|
--
|
||||||
|
hermes
commented
💡 Suggestion (non-blocking): worth a 💡 Suggestion (non-blocking): worth a `@DataJpaTest` that asserts this constraint rejects invalid rows — persist an Order with both `userId` and `guestToken` set, expecting a `DataIntegrityViolationException` referencing `chk_user_or_guest`. The `@PrePersist` in `Order.onCreate()` already prevents the neither-set case at the application layer, so the both-set rejection is the untested, highest-value path.
|
|||||||
|
-- The Order Javadoc states "Either userId or guestToken is set; never both,
|
||||||
|
hermes
commented
💡 Suggestion (non-blocking): worth a 💡 Suggestion (non-blocking): worth a `@DataJpaTest` that asserts this constraint rejects invalid rows — persist an Order with both `userId` and `guestToken` set, expecting a `DataIntegrityViolationException` referencing `chk_user_or_guest`. The `@PrePersist` in `Order.onCreate()` already prevents the neither-set case at the application layer, so the both-set rejection is the untested, highest-value path.
|
|||||||
|
-- never neither", but previously only the @PrePersist lifecycle callback
|
||||||
|
hermes
commented
💡 Suggestion (non-blocking): worth a 💡 Suggestion (non-blocking): worth a `@DataJpaTest` that asserts this constraint rejects invalid rows — persist an Order with both `userId` and `guestToken` set, expecting a `DataIntegrityViolationException` referencing `chk_user_or_guest`. The `@PrePersist` in `Order.onCreate()` already prevents the neither-set case at the application layer, so the both-set rejection is the untested, highest-value path.
|
|||||||
|
-- enforced this in Java. A stray INSERT (admin tooling, manual SQL) could
|
||||||
|
hermes
commented
💡 Suggestion (non-blocking): worth a 💡 Suggestion (non-blocking): worth a `@DataJpaTest` that asserts this constraint rejects invalid rows — persist an Order with both `userId` and `guestToken` set, expecting a `DataIntegrityViolationException` referencing `chk_user_or_guest`. The `@PrePersist` in `Order.onCreate()` already prevents the neither-set case at the application layer, so the both-set rejection is the untested, highest-value path.
|
|||||||
|
-- violate it silently.
|
||||||
|
hermes
commented
💡 Suggestion (non-blocking): worth a 💡 Suggestion (non-blocking): worth a `@DataJpaTest` that asserts this constraint rejects invalid rows — persist an Order with both `userId` and `guestToken` set, expecting a `DataIntegrityViolationException` referencing `chk_user_or_guest`. The `@PrePersist` in `Order.onCreate()` already prevents the neither-set case at the application layer, so the both-set rejection is the untested, highest-value path.
|
|||||||
|
--
|
||||||
|
hermes
commented
💡 Suggestion (non-blocking): worth a 💡 Suggestion (non-blocking): worth a `@DataJpaTest` that asserts this constraint rejects invalid rows — persist an Order with both `userId` and `guestToken` set, expecting a `DataIntegrityViolationException` referencing `chk_user_or_guest`. The `@PrePersist` in `Order.onCreate()` already prevents the neither-set case at the application layer, so the both-set rejection is the untested, highest-value path.
|
|||||||
|
-- The CHECK expression (user_id IS NULL) <> (guest_token IS NULL) evaluates:
|
||||||
|
hermes
commented
💡 Suggestion (non-blocking): worth a 💡 Suggestion (non-blocking): worth a `@DataJpaTest` that asserts this constraint rejects invalid rows — persist an Order with both `userId` and `guestToken` set, expecting a `DataIntegrityViolationException` referencing `chk_user_or_guest`. The `@PrePersist` in `Order.onCreate()` already prevents the neither-set case at the application layer, so the both-set rejection is the untested, highest-value path.
|
|||||||
|
-- TRUE when exactly one column is NULL (the other is set) — allowed
|
||||||
|
hermes
commented
💡 Suggestion (non-blocking): worth a 💡 Suggestion (non-blocking): worth a `@DataJpaTest` that asserts this constraint rejects invalid rows — persist an Order with both `userId` and `guestToken` set, expecting a `DataIntegrityViolationException` referencing `chk_user_or_guest`. The `@PrePersist` in `Order.onCreate()` already prevents the neither-set case at the application layer, so the both-set rejection is the untested, highest-value path.
|
|||||||
|
-- FALSE when both are NULL or both are set — rejected
|
||||||
|
hermes
commented
💡 Suggestion (non-blocking): worth a 💡 Suggestion (non-blocking): worth a `@DataJpaTest` that asserts this constraint rejects invalid rows — persist an Order with both `userId` and `guestToken` set, expecting a `DataIntegrityViolationException` referencing `chk_user_or_guest`. The `@PrePersist` in `Order.onCreate()` already prevents the neither-set case at the application layer, so the both-set rejection is the untested, highest-value path.
|
|||||||
|
--
|
||||||
|
hermes
commented
💡 Suggestion (non-blocking): worth a 💡 Suggestion (non-blocking): worth a `@DataJpaTest` that asserts this constraint rejects invalid rows — persist an Order with both `userId` and `guestToken` set, expecting a `DataIntegrityViolationException` referencing `chk_user_or_guest`. The `@PrePersist` in `Order.onCreate()` already prevents the neither-set case at the application layer, so the both-set rejection is the untested, highest-value path.
|
|||||||
|
-- Standard SQL CHECK constraint, supported by both H2 (tests/dev) and
|
||||||
|
hermes
commented
💡 Suggestion (non-blocking): worth a 💡 Suggestion (non-blocking): worth a `@DataJpaTest` that asserts this constraint rejects invalid rows — persist an Order with both `userId` and `guestToken` set, expecting a `DataIntegrityViolationException` referencing `chk_user_or_guest`. The `@PrePersist` in `Order.onCreate()` already prevents the neither-set case at the application layer, so the both-set rejection is the untested, highest-value path.
|
|||||||
|
-- PostgreSQL (prod).
|
||||||
|
hermes
commented
💡 Suggestion (non-blocking): worth a 💡 Suggestion (non-blocking): worth a `@DataJpaTest` that asserts this constraint rejects invalid rows — persist an Order with both `userId` and `guestToken` set, expecting a `DataIntegrityViolationException` referencing `chk_user_or_guest`. The `@PrePersist` in `Order.onCreate()` already prevents the neither-set case at the application layer, so the both-set rejection is the untested, highest-value path.
|
|||||||
|
ALTER TABLE orders
|
||||||
|
hermes
commented
💡 Suggestion (non-blocking): worth a 💡 Suggestion (non-blocking): worth a `@DataJpaTest` that asserts this constraint rejects invalid rows — persist an Order with both `userId` and `guestToken` set, expecting a `DataIntegrityViolationException` referencing `chk_user_or_guest`. The `@PrePersist` in `Order.onCreate()` already prevents the neither-set case at the application layer, so the both-set rejection is the untested, highest-value path.
|
|||||||
|
ADD CONSTRAINT chk_user_or_guest
|
||||||
|
hermes
commented
💡 Suggestion (non-blocking): worth a 💡 Suggestion (non-blocking): worth a `@DataJpaTest` that asserts this constraint rejects invalid rows — persist an Order with both `userId` and `guestToken` set, expecting a `DataIntegrityViolationException` referencing `chk_user_or_guest`. The `@PrePersist` in `Order.onCreate()` already prevents the neither-set case at the application layer, so the both-set rejection is the untested, highest-value path.
|
|||||||
|
CHECK ((user_id IS NULL) <> (guest_token IS NULL));
|
||||||
|
hermes
commented
💡 Suggestion (non-blocking): worth a 💡 Suggestion (non-blocking): worth a `@DataJpaTest` that asserts this constraint rejects invalid rows — persist an Order with both `userId` and `guestToken` set, expecting a `DataIntegrityViolationException` referencing `chk_user_or_guest`. The `@PrePersist` in `Order.onCreate()` already prevents the neither-set case at the application layer, so the both-set rejection is the untested, highest-value path.
|
|||||||
💡 Suggestion (non-blocking): worth a
@DataJpaTestthat asserts this constraint rejects invalid rows — persist an Order with bothuserIdandguestTokenset, expecting aDataIntegrityViolationExceptionreferencingchk_user_or_guest. The@PrePersistinOrder.onCreate()already prevents the neither-set case at the application layer, so the both-set rejection is the untested, highest-value path.