Skip to content

Scrum 150 pri real time pricing price alerts#15

Merged
Giabaongo merged 9 commits into
devfrom
SCRUM-150-PRI-Real-time-Pricing-Price-Alerts
Jun 17, 2026
Merged

Scrum 150 pri real time pricing price alerts#15
Giabaongo merged 9 commits into
devfrom
SCRUM-150-PRI-Real-time-Pricing-Price-Alerts

Conversation

@Giabaongo

@Giabaongo Giabaongo commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

Release Notes

  • New Features

    • Optimistic concurrency handling for concurrent price updates
  • Bug Fixes

    • Stricter validation now rejects zero or negative product prices
    • Improved duplicate product-market detection with proper 409 conflict responses
    • Enhanced validation error messages for date parameters in price history queries
  • Changes

    • Increased maximum page size limit from 100 to 200 products per request

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.
@kody-ai

kody-ai Bot commented Jun 17, 2026

Copy link
Copy Markdown

Code Review Could Not Complete ⚠️

The review failed before suggestions could be generated.

Reason: The configured API key (google_gemini) is out of credits or has hit its billing limit. Top up the account or adjust the plan.

After fixing the issue, comment @kody review on this PR to re-run the review.

Kody Guide: Usage and Configuration
Interacting with Kody
  • Request a Review: Ask Kody to review your PR manually by adding a comment with the @kody start-review command at the root of your PR.

  • Validate Business Logic: Ask Kody to validate your code against business rules by adding a comment with the @kody -v business-logic command.

  • Provide Feedback: Help Kody learn and improve by reacting to its comments with a 👍 for helpful suggestions or a 👎 if improvements are needed.

Current Kody Configuration
Review Options

The following review options are enabled or disabled:

Options Enabled
Bug
Performance
Security
Business Logic

Access your configuration settings here.

@coderabbitai

coderabbitai Bot commented Jun 17, 2026

Copy link
Copy Markdown

Review Change Stack

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Free

Run ID: daa7a05e-5295-4d22-84cf-f1577a3a34c5

📥 Commits

Reviewing files that changed from the base of the PR and between da1b71d and 94e9da3.

📒 Files selected for processing (20)
  • src/FreshFlow.API/Controllers/MarketsController.cs
  • src/FreshFlow.API/Program.cs
  • src/Modules/Catalog/FreshFlow.Catalog.Application/Queries/Products/GetProducts/GetProductsQueryValidator.cs
  • src/Modules/Pricing/FreshFlow.Pricing.Application/Abstractions/ConcurrencyConflictException.cs
  • src/Modules/Pricing/FreshFlow.Pricing.Application/Abstractions/DuplicateMarketProductException.cs
  • src/Modules/Pricing/FreshFlow.Pricing.Application/Abstractions/IMarketProductRepository.cs
  • src/Modules/Pricing/FreshFlow.Pricing.Application/Commands/CreateMarketProduct/CreateMarketProductCommandHandler.cs
  • src/Modules/Pricing/FreshFlow.Pricing.Application/Commands/UpdateAvailableQuantity/UpdateAvailableQuantityCommandHandler.cs
  • src/Modules/Pricing/FreshFlow.Pricing.Application/Commands/UpdateProductPrice/UpdateProductPriceCommandHandler.cs
  • src/Modules/Pricing/FreshFlow.Pricing.Domain/Entities/MarketProduct.cs
  • src/Modules/Pricing/FreshFlow.Pricing.Infrastructure/FreshFlow.Pricing.Infrastructure.csproj
  • src/Modules/Pricing/FreshFlow.Pricing.Infrastructure/Persistence/Configurations/MarketProductConfiguration.cs
  • src/Modules/Pricing/FreshFlow.Pricing.Infrastructure/Repositories/MarketProductRepository.cs
  • src/Modules/Pricing/FreshFlow.Pricing.Infrastructure/Repositories/PriceSnapshotRepository.cs
  • tests/Unit/FreshFlow.Catalog.UnitTests/Products/GetProductsQueryValidatorTests.cs
  • tests/Unit/FreshFlow.Pricing.UnitTests/Commands/CreateMarketProductCommandHandlerTests.cs
  • tests/Unit/FreshFlow.Pricing.UnitTests/Commands/UpdateProductPriceCommandHandlerTests.cs
  • tests/Unit/FreshFlow.Pricing.UnitTests/Domain/MarketProductTests.cs
  • tests/Unit/FreshFlow.Pricing.UnitTests/Persistence/PriceSnapshotRepositoryPageSizeTests.cs
  • tests/Unit/FreshFlow.Pricing.UnitTests/Queries/GetPriceChangeHistoryQueryHandlerTests.cs

📝 Walkthrough

Walkthrough

The PR introduces optimistic concurrency safety for the Pricing module by marking UpdatedAt as an EF concurrency token, adding ConcurrencyConflictException and DuplicateMarketProductException application abstractions, translating Postgres errors in MarketProductRepository, and mapping those exceptions to 409 results in three command handlers. Additional changes tighten the MarketProduct zero-price guard, fix the CORS fluent chain, sanitize validation error messages, and raise the Catalog PageSize limit to 200.


Pricing: optimistic concurrency, duplicate detection, and domain hardening

Layer / File(s) Summary
Application exception abstractions and repository interface
src/Modules/Pricing/FreshFlow.Pricing.Application/Abstractions/ConcurrencyConflictException.cs, src/Modules/Pricing/FreshFlow.Pricing.Application/Abstractions/DuplicateMarketProductException.cs, src/Modules/Pricing/FreshFlow.Pricing.Application/Abstractions/IMarketProductRepository.cs
Adds two new sealed exception types (ConcurrencyConflictException, DuplicateMarketProductException) and extends IMarketProductRepository with the atomic AddAndSaveAsync method documenting the unique-constraint throw contract.
Domain: zero-price guard and parameterized tests
src/Modules/Pricing/FreshFlow.Pricing.Domain/Entities/MarketProduct.cs, tests/Unit/FreshFlow.Pricing.UnitTests/Domain/MarketProductTests.cs
ValidatePrice now rejects zero in addition to negative values (<= 0). Three unit tests are converted to [Theory] with [InlineData(0)] and [InlineData(-1)] to cover the new boundary.
Infrastructure: EF concurrency token, Postgres error translation, and snapshot limit
src/Modules/Pricing/FreshFlow.Pricing.Infrastructure/FreshFlow.Pricing.Infrastructure.csproj, src/Modules/Pricing/FreshFlow.Pricing.Infrastructure/Persistence/Configurations/MarketProductConfiguration.cs, src/Modules/Pricing/FreshFlow.Pricing.Infrastructure/Repositories/MarketProductRepository.cs, src/Modules/Pricing/FreshFlow.Pricing.Infrastructure/Repositories/PriceSnapshotRepository.cs
Adds Npgsql.EntityFrameworkCore.PostgreSQL package, marks UpdatedAt as EF concurrency token, adds AddAndSaveAsync with SqlState 23505DuplicateMarketProductException translation, wraps SaveChangesAsync to convert DbUpdateConcurrencyExceptionConcurrencyConflictException, and bounds GetByMarketProductIdAsync to 100 rows.
Command handlers: 409 mapping for duplicate and concurrency exceptions
src/Modules/Pricing/FreshFlow.Pricing.Application/Commands/CreateMarketProduct/CreateMarketProductCommandHandler.cs, src/Modules/Pricing/FreshFlow.Pricing.Application/Commands/UpdateAvailableQuantity/UpdateAvailableQuantityCommandHandler.cs, src/Modules/Pricing/FreshFlow.Pricing.Application/Commands/UpdateProductPrice/UpdateProductPriceCommandHandler.cs
CreateMarketProductCommandHandler uses AddAndSaveAsync and catches DuplicateMarketProductException as MARKET_PRODUCT_ALREADY_EXISTS. Both update handlers wrap SaveChangesAsync in try/catch for ConcurrencyConflictException, returning OPTIMISTIC_CONCURRENCY_CONFLICT.
Command handler, persistence, and query handler tests
tests/Unit/FreshFlow.Pricing.UnitTests/Commands/CreateMarketProductCommandHandlerTests.cs, tests/Unit/FreshFlow.Pricing.UnitTests/Commands/UpdateProductPriceCommandHandlerTests.cs, tests/Unit/FreshFlow.Pricing.UnitTests/Persistence/PriceSnapshotRepositoryPageSizeTests.cs, tests/Unit/FreshFlow.Pricing.UnitTests/Queries/GetPriceChangeHistoryQueryHandlerTests.cs
Updates CreateMarketProductCommandHandlerTests to assert AddAndSaveAsync and simulate DuplicateMarketProductException; adds a ConcurrencyConflictException test for UpdateProductPriceCommandHandler; adds cursor-resilience and a date-range mapping regression test.

API and Catalog minor fixes

Layer / File(s) Summary
CORS chain fix, validation message sanitization, and PageSize increase
src/FreshFlow.API/Program.cs, src/FreshFlow.API/Controllers/MarketsController.cs, src/Modules/Catalog/FreshFlow.Catalog.Application/Queries/Products/GetProducts/GetProductsQueryValidator.cs, tests/Unit/FreshFlow.Catalog.UnitTests/Products/GetProductsQueryValidatorTests.cs
CORS AllowFrontend policy now chains AllowCredentials correctly. from/to validation errors no longer echo raw user input. GetProductsQueryValidator raises PageSize upper bound from 100 to 200, with matching test updates.

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
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐇 A zero price? Forbidden now, I say!
Two exceptions hop in, guarding the way.
UpdatedAt watches with tokens so keen,
No stale concurrent writes slip past unseen.
The CORS chain is mended, the cursor made tough —
This bunny's backend is plenty robust enough! 🌿


Note

🎁 Summarized by CodeRabbit Free

Your 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 @coderabbitai help to get the list of available commands and usage tips.

@Giabaongo Giabaongo merged commit 09d3623 into dev Jun 17, 2026
5 of 6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant