feat(request): add optional idempotency key for request creation#72
Merged
Conversation
Add optional idempotencyKey (UUID) field to CreateClientRequest. When provided, it is used as the request id instead of generating a new UUID. Duplicate idempotency key gracefully returns the existing request instead of failing. Unit and integration tests cover positive scenarios (key used as id, null key generates new UUID, different keys create separate requests) and negative scenarios (duplicate key returns existing request, unrelated DB errors propagate).
- Add idempotency_key column (UNIQUE, nullable) to request table via V5 migration - Server always generates request.id; idempotency key stored in separate column - Validate payload equality on duplicate idempotency key; throw IdempotencyKeyConflictException (409) on mismatch - Detect duplicate key via DuplicateKeyException and SQL state 23505 instead of fragile message string matching - Add IdempotencyKeyConflictException with GlobalExceptionHandler support - Add localized error messages (en/ru) for idempotency conflict - Update unit and integration tests for new flow
Owner
Author
Summary
Changes
Review feedback addressed
TestsUnit tests (RequestServiceTest):
Integration tests (RequestIntegrationIT):
|
- Remove idempotency_key column, IdempotencyKeyConflictException, and related migration — idempotency key is stored as Request.id, not a separate column - When idempotencyKey provided in CreateClientRequest, use it as Request.id instead of generating a new UUID - On DuplicateKeyException (same key reused), return existing request - Use DuplicateKeyException instead of fragile message string matching
When the same idempotency key is reused with a different payload, return 409 Conflict instead of silently returning the existing request. This ensures correct idempotency semantics: same key + same payload = idempotent; same key + different payload = error.
…atus
- Add auth_client_id column to request table via V5 migration
- Store clientId from token in Request.authClientId on creation
- On duplicate idempotency key, verify both payload AND authClientId
match before returning existing request; otherwise 409 Conflict
- On GET /api/requests/{id}, verify authClientId matches; otherwise 404
- Controllers extract clientId from SecurityService reactive chain
- Add unit and integration tests for ownership validation
response_data is always NULL on insert, so hardcode it in the SQL query and remove it as a method parameter, satisfying the SonarQube rule limiting method parameters to 7.
… UUID tests
- Handle HttpMessageNotReadableException in GlobalExceptionHandler
returning 400 with localized error.request.invalidPayload message
- Add error.request.invalidPayload to all messages*.properties (en/ru)
- Add integration tests for invalid UUID in GET /api/requests/{id}
- Add integration test for invalid idempotencyKey UUID in POST /api/clients
- Add unit test for HttpMessageNotReadableException handler
Existing rows get 'unknown' as default; model default matches.
Column auth_client_id is NOT NULL, so the test must supply a value.
…le reads - Merge V6 into V5: composite PK (id, auth_client_id) in one migration - getRequestStatus uses findByIdAndAuthClientId; falls back to findById for 'unknown' auth_client_id (transitional period for old rows) - markCompleted/markFailed include auth_client_id in WHERE clause - Worker processClaimedRequest passes authClientId to mark methods - Tests updated for new signatures and composite PK behavior - Different clients can now have separate requests with the same idempotency key (idempotency is per-client)
- V5 migration: just adds auth_client_id column (NOT NULL DEFAULT 'unknown'), no composite PK or unique index needed — id is already unique PK - Worker queries (claim, reclaim) are correct as-is since id is unique - markCompleted/markFailed include auth_client_id in WHERE for safety - RequestWorkerRetryIT: set authClientId on Request builder to fix NPE - getRequestStatus uses findByIdAndAuthClientId with findById fallback for backward-compatible reads of 'unknown' auth_client_id rows
- API.md: document optional idempotencyKey field, idempotency semantics, and 409 Conflict on payload mismatch - CHANGELOG.md: add idempotency key, auth_client_id, HttpMessageNotReadable exception handler, and new error messages - ClientController: update @operation description for idempotency key
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Summary
idempotencyKey(UUID) field toCreateClientRequestDTOidinstead of generating a new UUIDChanges
CreateClientRequest: added optionalUUID idempotencyKeyfieldRequestService.submitClientCreateRequest: uses idempotency key as request ID when present; handles duplicate key constraint violations by returning the existing requestCreateClientRequestconstructor calls across test filesTests added
Unit tests (RequestServiceTest):
submitClientCreateRequest_withIdempotencyKey_usesKeyAsRequestIdsubmitClientCreateRequest_withoutIdempotencyKey_generatesNewUuidsubmitClientCreateRequest_duplicateIdempotencyKey_returnsExistingRequestsubmitClientCreateRequest_duplicateIdempotencyKey_returnsExistingRequestWhenCompletedsubmitClientCreateRequest_nonIdempotencyDuplicateKeyError_propagatesOriginalErrorIntegration tests (RequestIntegrationIT):
create_client_request_with_idempotencyKey_uses_key_as_request_idcreate_client_request_duplicate_idempotencyKey_returns_existing_requestcreate_client_request_without_idempotencyKey_generates_new_idcreate_client_request_with_idempotencyKey_different_keys_create_separate_requests