Scrum 150 pri real time pricing price alerts#15
Conversation
Validator capped PageSize at 100 which rejected admin dropdown requests for pageSize=200, returning 400 VALIDATION_ERROR.
…duct updates
- MarketProductConfiguration: add .IsConcurrencyToken() to UpdatedAt so EF Core
includes it in the UPDATE WHERE clause (no schema change, no migration required)
- ConcurrencyConflictException: new application-level exception in
Pricing.Application/Abstractions — keeps the Application layer free of EF Core
- MarketProductRepository.SaveChangesAsync (Infrastructure): catches EF Core's
DbUpdateConcurrencyException and re-throws as ConcurrencyConflictException,
keeping the EF dependency strictly within Infrastructure
- UpdateProductPriceCommandHandler: catches ConcurrencyConflictException →
returns Result.Failure(Error.Conflict("OPTIMISTIC_CONCURRENCY_CONFLICT", ...))
alongside the existing ExpectedVersion pre-check (defense-in-depth)
- UpdateAvailableQuantityCommandHandler: same pattern applied
- Pricing.Application.csproj: NO EF Core reference added; dependency rules upheld
- DuplicateMarketProductException: new application-level exception in
Pricing.Application/Abstractions — same pattern as ConcurrencyConflictException
- IMarketProductRepository: add AddAndSaveAsync(marketProduct, ct) contract
- MarketProductRepository.AddAndSaveAsync (Infrastructure): inserts then saves;
catches DbUpdateException when inner is PostgresException with SqlState "23505"
(unique constraint violation) and re-throws as DuplicateMarketProductException;
other DbUpdateExceptions propagate unchanged; uses direct Npgsql.PostgresException
type check (compile-time safe) — Npgsql.EntityFrameworkCore.PostgreSQL added to
Infrastructure csproj as an explicit dependency
- CreateMarketProductCommandHandler: replaces AddAsync+SaveChangesAsync pair with
AddAndSaveAsync; catches DuplicateMarketProductException → returns
Error.Conflict("MARKET_PRODUCT_ALREADY_EXISTS") so concurrent races past the
pre-check return 409 instead of 500; pre-check retained as fast path
- Tests: fix stale AddAsync/SaveChangesAsync assertions → AddAndSaveAsync;
add Handle_ConcurrentDuplicate_ReturnsConflictAsync covering the race path;
add NSubstitute.ExceptionExtensions using for ThrowsAsync
- Pricing.Application.csproj: no new dependencies added
ValidatePrice tightened from price < 0 to price <= 0; message updated to "Price must be greater than zero." ValidateQuantity unchanged (quantity=0 is valid out-of-stock signal). Three [Fact] negative-price domain tests promoted to [Theory] covering both price=0 and price=-1 (242/242 green).
…idation errors 'from' and 'to' raw query-string values were interpolated into 400 error messages, leaking untrusted user input into API responses. Messages now use static strings: "'from' is not a valid ISO 8601 date." and "'to' is not a valid ISO 8601 date." Error code VALIDATION_ERROR unchanged.
Added .AllowCredentials() to the AllowFrontend CORS policy. Required for SignalR WebSocket/SSE negotiate when the browser sends cookies or Authorization headers with cross-origin requests. Compatible with the existing .WithOrigins(...) explicit-origins configuration (AllowCredentials is incompatible with AllowAnyOrigin, which is not used here).
GetByMarketProductIdAsync was unbounded and could scan the entire price_snapshots partition for a high-volume product. Added .Take(100) cap with a named constant. ORDER BY RecordedAt DESC / Id DESC was already present. Method is kept (not removed) because it is used by PriceHistoryAcceptanceTests; production read-side uses the paginated GetPageAsync instead.
…nge, and cursor 1. UpdateProductPriceCommandHandler: Handle_SaveChangesThrowsConcurrencyConflict_Returns409Conflict — SaveChangesAsync throws ConcurrencyConflictException → handler returns OPTIMISTIC_CONCURRENCY_CONFLICT (covers the DB-level EF token path, distinct from the pre-check version mismatch already tested). 2. GetPriceChangeHistoryQueryHandler: Handle_WithDateRange_ItemsAndNextCursorMappedThrough — With both From and To set, verifies items are mapped correctly AND NextCursor is forwarded through to the result DTO (combined regression for the date-range + mapping path). 3. PriceSnapshotRepository.GetPageAsync: GetPageAsync_InvalidOrTamperedCursor_TreatedAsStartOfList — Theory with 3 invalid cursor strings (bad base64, valid base64/bad JSON, empty string) verifies none throw and all fall back to start-of-list. 247/247 unit tests GREEN.
… 200 Commit f118193 raised GetProductsQueryValidator max pageSize from 100 to 200 but left the two boundary tests targeting the old limit. Updated Validate_PageSizeExceedsMax_Fails to use 201 and Validate_MaxPageSize_Passes to use 200 so both tests reflect the current validator rule.
Code Review Could Not Complete
|
| Options | Enabled |
|---|---|
| Bug | ✅ |
| Performance | ✅ |
| Security | ✅ |
| Business Logic | ✅ |
ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Free Run ID: 📒 Files selected for processing (20)
📝 WalkthroughWalkthroughThe PR introduces optimistic concurrency safety for the Pricing module by marking Pricing: optimistic concurrency, duplicate detection, and domain hardening
API and Catalog minor fixes
Sequence Diagram(s)sequenceDiagram
participant Client
participant CommandHandler
participant MarketProductRepository
participant Postgres
rect rgba(100, 149, 237, 0.5)
note over Client, Postgres: CreateMarketProduct (race condition path)
Client->>CommandHandler: Handle(CreateMarketProductCommand)
CommandHandler->>MarketProductRepository: AddAndSaveAsync(marketProduct, ct)
MarketProductRepository->>Postgres: INSERT market_product
Postgres-->>MarketProductRepository: PostgresException 23505 (unique violation)
MarketProductRepository-->>CommandHandler: throws DuplicateMarketProductException
CommandHandler-->>Client: Result.Failure(409 MARKET_PRODUCT_ALREADY_EXISTS)
end
rect rgba(144, 238, 144, 0.5)
note over Client, Postgres: UpdateProductPrice (concurrency conflict path)
Client->>CommandHandler: Handle(UpdateProductPriceCommand)
CommandHandler->>MarketProductRepository: SaveChangesAsync(ct)
MarketProductRepository->>Postgres: UPDATE market_product WHERE updated_at = expected
Postgres-->>MarketProductRepository: 0 rows affected (stale token)
MarketProductRepository-->>CommandHandler: throws ConcurrencyConflictException
CommandHandler-->>Client: Result.Failure(409 OPTIMISTIC_CONCURRENCY_CONFLICT)
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
Note 🎁 Summarized by CodeRabbit FreeYour organization is on the Free plan. CodeRabbit will generate a high-level summary and a walkthrough for each pull request. For a comprehensive line-by-line review, please upgrade your subscription to CodeRabbit Pro by visiting https://app.coderabbit.ai/login. Comment |
Summary by CodeRabbit
Release Notes
New Features
Bug Fixes
Changes