-
Notifications
You must be signed in to change notification settings - Fork 6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[WIP] #215 | Add soft-deletion support for Varadhi Resources #218
base: master
Are you sure you want to change the base?
Conversation
- Add Javadoc comments to the Constants class and its inner classes. - Add private constructors to inner classes to prevent instantiation.
- Added unit tests for VaradhiTopicService, covering topic creation, deletion (hard and soft), restoration, and existence checks. - Implemented tests for VaradhiTopic entity to verify its creation and status updates. - Added tests for VaradhiTopicHandler to ensure correct handling of topic related requests. - Refactored existing tests to reduce code duplication by extracting common setup logic into helper methods. - Ensured comprehensive coverage of various edge cases, such as filtering out inactive topics and handling meta store failures.
WalkthroughThis pull request introduces comprehensive changes across multiple files to enhance the Varadhi system's resource management, focusing on soft and hard deletion capabilities, status tracking, and improved validation for topics and subscriptions. The modifications span constants, entity definitions, services, and test cases, providing a more robust and flexible approach to managing system resources. Changes
Possibly related issues
Poem
✨ Finishing Touches
Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media? 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
🔭 Outside diff range comments (1)
server/src/main/java/com/flipkart/varadhi/services/SubscriptionService.java (1)
Line range hint
64-92
: Create subscription partial rollback
While the code handles exceptions, consider rolling back the newly created subscription if provisioning permanently fails.
🧹 Nitpick comments (37)
entities/src/main/java/com/flipkart/varadhi/entities/VaradhiSubscription.java (2)
167-172
: Restoration logic.
Restoring the subscription to CREATED is clear, but consider whether a new “RESTORED” state might improve traceability.
174-190
: Consider supplying a default or optional for missing property values.
Throwing an exception is valid, though returning an optional or fallback could be more user-friendly in certain scenarios.common/src/main/java/com/flipkart/varadhi/Constants.java (5)
23-23
: Consider making DefaultTopicCapacity immutable.The
DefaultTopicCapacity
constant could potentially be modified sinceTopicCapacityPolicy
might be mutable.Consider making
TopicCapacityPolicy
immutable or creating an unmodifiable instance:- public static final TopicCapacityPolicy DefaultTopicCapacity = new TopicCapacityPolicy(100, 400, 2); + public static final TopicCapacityPolicy DefaultTopicCapacity = Collections.unmodifiableObject(new TopicCapacityPolicy(100, 400, 2));
53-54
: Enhance documentation for query parameters.While the parameters are self-explanatory, adding documentation about valid values and usage would be helpful, especially for
deletionType
which is crucial for the new deletion feature.Add parameter documentation:
+ /** Specifies the type of deletion operation (e.g., "soft", "hard") */ public static final String QUERY_PARAM_DELETION_TYPE = "deletionType"; + /** When true, allows deletion even if there are dependent resources */ public static final String QUERY_PARAM_IGNORE_CONSTRAINTS = "ignoreConstraints";
83-93
: Consider using standard HTTP status code constants.Instead of defining custom HTTP status code constants, consider using
HttpStatus
from Spring Framework or similar standard libraries if available in the project.- public static final int HTTP_RATE_LIMITED = 429; - public static final int HTTP_UNPROCESSABLE_ENTITY = 422; + // Use org.springframework.http.HttpStatus if Spring is available + public static final int HTTP_RATE_LIMITED = HttpStatus.TOO_MANY_REQUESTS.value(); + public static final int HTTP_UNPROCESSABLE_ENTITY = HttpStatus.UNPROCESSABLE_ENTITY.value();
96-118
: Consider expanding metrics structure for deletion feature.Since this PR implements deletion functionality, consider adding corresponding metrics.
Example structure:
public static class Deletion { public static final String SOFT_DELETION_METER = "deletion.soft"; public static final String HARD_DELETION_METER = "deletion.hard"; public static final String RESTORATION_METER = "deletion.restore"; private Deletion() { // Private constructor to prevent instantiation } }
Line range hint
126-129
: Document rationale for size limits.The maximum sizes for payload and headers would benefit from documentation explaining their rationale.
Add explanatory comments:
+ // 5MB max payload size to balance between large messages and server resource usage public static final int PAYLOAD_SIZE_MAX = 5 * 1024 * 1024; + // Limited number of headers to prevent header explosion attacks public static final int HEADERS_ALLOWED_MAX = 10; + // Header size limits aligned with common proxy server defaults public static final int HEADER_NAME_SIZE_MAX = 64; public static final int HEADER_VALUE_SIZE_MAX = 256;server/src/test/java/com/flipkart/varadhi/services/VaradhiTopicServiceTest.java (4)
45-48
: Consider moving test constants to a separate class.These constants could be reused across other test classes. Consider moving them to a dedicated test constants class for better maintainability and reusability.
67-69
: Consider using@ExtendWith(MockitoExtension.class)
.Instead of manually calling
MockitoAnnotations.openMocks(this)
, you can use the@ExtendWith(MockitoExtension.class)
annotation at the class level for a more idiomatic JUnit 5 approach.+@ExtendWith(MockitoExtension.class) class VaradhiTopicServiceTest { // ... @BeforeEach public void setUp() { - MockitoAnnotations.openMocks(this); varadhiTopicFactory = spy(new VaradhiTopicFactory(storageTopicFactory, REGION, DEFAULT_CAPACITY_POLICY)); // ... }
162-179
: Enhance topic-in-use test with specific error message assertion.The test verifies the exception type but doesn't validate the error message. Add an assertion to verify the specific error message for better test coverage.
Exception exception = assertThrows( InvalidOperationForResourceException.class, () -> varadhiTopicService.delete(varadhiTopic.getName(), ResourceDeletionType.HARD_DELETE) ); verify(metaStore, never()).deleteTopic(varadhiTopic.getName()); assertEquals(InvalidOperationForResourceException.class, exception.getClass()); +assertEquals("Cannot delete topic " + varadhiTopic.getName() + " as it has active subscriptions", exception.getMessage());
323-330
: Add input validation tosetupMockTopics
.The method assumes that
topicNames
andtopicStatuses
lists have the same size. Add validation to prevent potentialIndexOutOfBoundsException
.private void setupMockTopics(String projectName, List<String> topicNames, List<Boolean> topicStatuses) { + if (topicNames.size() != topicStatuses.size()) { + throw new IllegalArgumentException("Topic names and statuses lists must have the same size"); + } when(metaStore.getTopicNames(projectName)).thenReturn(topicNames); for (int i = 0; i < topicNames.size(); i++) { // ... } }server/src/main/java/com/flipkart/varadhi/services/VaradhiTopicService.java (6)
15-17
: Expanded class-level documentation
The Javadoc succinctly describes the service’s purpose. Consider mentioning soft/hard deletion capabilities for completeness.
52-67
: Active status check
ThrowingResourceNotFoundException
for inactive topics is sensible. Logging the inactive status could improve debugging.
69-70
: Method documentation
Comments are clear about topic deletion. Optionally referencing the newResourceDeletionType
param would further clarify usage.
88-96
: Soft delete concurrency
Marking a topic inactive is straightforward. If multiple soft-delete requests arrive concurrently, consider whether extra idempotency checks are needed.
117-134
: Restore method
Good flow: checks if active, throws if not deleted, updates status to ACTIVE, and saves. Optionally log a final success message.
136-154
: Validation performance
Scanning all subscription names might affect performance at scale. Consider more direct checks or indexing in future iterations.server/src/main/java/com/flipkart/varadhi/services/SubscriptionService.java (5)
116-146
: Update subscription logic
Revertinggrouped
mid-operation can cause confusion. Consider a clearer approach to reduce potential state flip-flops.
157-186
: Delete subscription
Hard vs. soft deletion is clearly distinguished. Validate concurrency if a subscription is in a transitional state (e.g., provisioning).
235-251
:getValidatedSubscription
duplication
Ensures subscription validity. Consider unifying all state checks in one method to minimize code duplication.
283-302
:performSubscriptionOperation
A neat approach to reduce duplication. Watch for concurrency issues if subscription states change rapidly.
327-335
: handleSoftDelete
Straightforward approach. You might add an additional check for already-inactive subscriptions.server/src/main/java/com/flipkart/varadhi/web/v1/admin/SubscriptionHandlers.java (3)
144-176
: Hierarchy construction
Generating subscription and topic hierarchies is straightforward. Consider caching repeated calls toprojectService.getProject
if performance becomes a concern.
181-185
: List endpoint
Fetching all subscription names might be expensive for large projects. Consider pagination or search filters if needed.
204-218
: Create endpoint
UsesvaradhiSubscriptionFactory
and checks the subscribed topic. Adding more explicit error logs on creation failure might help troubleshooting.entities/src/main/java/com/flipkart/varadhi/entities/ResourceDeletionType.java (2)
6-9
: Add documentation for enum values.Consider adding Javadoc for each enum value to clearly document their intended use and behavior.
public enum ResourceDeletionType { + /** Marks the resource as inactive without removing it from the database */ SOFT_DELETE, + /** Permanently removes the resource from the database */ HARD_DELETE, + /** Default deletion type, typically maps to SOFT_DELETE for safety */ DEFAULT;
18-27
: Enhance string parsing robustness.The string parsing could be more robust by:
- Using a constant for the default value
- Adding case-insensitive matching
+ /** Default deletion type used when input is invalid */ + private static final ResourceDeletionType DEFAULT_TYPE = DEFAULT; + public static ResourceDeletionType fromValue(String value) { if (value == null || value.isBlank()) { - return DEFAULT; + return DEFAULT_TYPE; } - return switch (value.trim().toUpperCase()) { - case "SOFT_DELETE" -> SOFT_DELETE; - case "HARD_DELETE" -> HARD_DELETE; - default -> DEFAULT; + try { + return valueOf(value.trim().toUpperCase()); + } catch (IllegalArgumentException e) { + return DEFAULT_TYPE; + } }spi/src/main/java/com/flipkart/varadhi/spi/db/MetaStore.java (1)
52-52
: Add method documentation.Add Javadoc for the new
updateTopic
method to document its purpose, parameters, and any exceptions it might throw.+ /** + * Updates an existing topic in the meta store. + * + * @param topic the topic to update + * @throws IllegalArgumentException if the topic is null + * @throws ResourceNotFoundException if the topic doesn't exist + */ void updateTopic(VaradhiTopic topic);entities/src/main/java/com/flipkart/varadhi/entities/VaradhiTopic.java (2)
20-20
: Consider making status field final.The status field should be made final to ensure immutability, with status changes handled through creating new instances.
- private Status status; + private final Status status;
105-110
: Add status transition validation.Consider adding validation for status transitions to prevent invalid state changes (e.g., INACTIVE to INACTIVE).
public void updateStatus(Status newStatus) { if (newStatus == null) { throw new IllegalArgumentException("Status cannot be null"); } + if (this.status == newStatus) { + throw new IllegalArgumentException("Topic is already in " + newStatus + " state"); + } this.status = newStatus; }entities/src/test/java/com/flipkart/varadhi/entities/VaradhiTopicTest.java (2)
109-118
: Enhance status transition test coverage.Consider adding parameterized tests for status transitions and edge cases.
+ @ParameterizedTest + @CsvSource({ + "ACTIVE,INACTIVE", + "INACTIVE,ACTIVE" + }) + void updateStatus_ValidTransitions_Succeeds(Status from, Status to) { + VaradhiTopic varadhiTopic = createDefaultVaradhiTopic(false); + varadhiTopic.updateStatus(from); + + varadhiTopic.updateStatus(to); + + assertEquals(to, varadhiTopic.getStatus()); + } + + @Test + void updateStatus_SameState_ThrowsException() { + VaradhiTopic varadhiTopic = createDefaultVaradhiTopic(false); + + assertThrows(IllegalArgumentException.class, + () -> varadhiTopic.updateStatus(Status.ACTIVE), + "Updating to same state should throw exception"); + }
27-29
: Consider adding validation in helper method.The helper method should validate input parameters.
private VaradhiTopic createDefaultVaradhiTopic(boolean grouped) { + Objects.requireNonNull(PROJECT_NAME, "Project name cannot be null"); + Objects.requireNonNull(TOPIC_NAME, "Topic name cannot be null"); + Objects.requireNonNull(TOPIC_CAPACITY, "Topic capacity cannot be null"); return VaradhiTopic.of(PROJECT_NAME, TOPIC_NAME, grouped, TOPIC_CAPACITY); }server/src/test/java/com/flipkart/varadhi/web/admin/SubscriptionHandlersTest.java (1)
166-171
: Enhance test coverage for deletion types.The test verifies the basic deletion functionality but doesn't test different deletion types. Consider adding test cases for both soft and hard deletion scenarios.
Apply this diff to add test coverage for different deletion types:
- @Test - void testSubscriptionDelete() throws InterruptedException { - HttpRequest<Buffer> request = createRequest(HttpMethod.DELETE, getSubscriptionUrl("sub1", project)); - SubscriptionResource resource = getSubscriptionResource("sub1", project, topicResource); - - doReturn(project).when(projectService).getCachedProject(project.getName()); - ArgumentCaptor<String> captorSubName = ArgumentCaptor.forClass(String.class); - ArgumentCaptor<Project> captorProject = ArgumentCaptor.forClass(Project.class); - doReturn(CompletableFuture.completedFuture(null)).when(subscriptionService) - .deleteSubscription(captorSubName.capture(), captorProject.capture(), any(), any()); - - sendRequestWithoutBody(request, null); - assertEquals(captorSubName.getValue(), resource.getSubscriptionInternalName()); - assertEquals(captorProject.getValue().getName(), project.getName()); - verify(subscriptionService, times(1)).deleteSubscription(any(), any(), any(), any()); - } + @Test + void testSubscriptionSoftDelete() throws InterruptedException { + verifyDeleteRequest("SOFT_DELETE", ResourceDeletionType.SOFT_DELETE); + } + + @Test + void testSubscriptionHardDelete() throws InterruptedException { + verifyDeleteRequest("HARD_DELETE", ResourceDeletionType.HARD_DELETE); + } + + @Test + void testSubscriptionDeleteDefaultType() throws InterruptedException { + verifyDeleteRequest(null, ResourceDeletionType.SOFT_DELETE); + } + + private void verifyDeleteRequest(String deletionType, ResourceDeletionType expectedDeletionType) + throws InterruptedException { + String url = getSubscriptionUrl("sub1", project); + if (deletionType != null) { + url += "?deletionType=" + deletionType; + } + + HttpRequest<Buffer> request = createRequest(HttpMethod.DELETE, url); + SubscriptionResource resource = getSubscriptionResource("sub1", project, topicResource); + + doReturn(project).when(projectService).getCachedProject(project.getName()); + ArgumentCaptor<String> captorSubName = ArgumentCaptor.forClass(String.class); + ArgumentCaptor<Project> captorProject = ArgumentCaptor.forClass(Project.class); + ArgumentCaptor<ResourceDeletionType> captorDeletionType = ArgumentCaptor.forClass(ResourceDeletionType.class); + + doReturn(CompletableFuture.completedFuture(null)).when(subscriptionService) + .deleteSubscription(captorSubName.capture(), captorProject.capture(), any(), captorDeletionType.capture()); + + sendRequestWithoutBody(request, null); + + assertEquals(captorSubName.getValue(), resource.getSubscriptionInternalName()); + assertEquals(captorProject.getValue().getName(), project.getName()); + assertEquals(expectedDeletionType, captorDeletionType.getValue()); + verify(subscriptionService, times(1)).deleteSubscription(any(), any(), any(), any()); + }server/src/main/java/com/flipkart/varadhi/web/v1/admin/TopicHandlers.java (1)
217-229
: Improve error message clarity.The error message could be more descriptive to help users understand the issue better.
Apply this diff to enhance the error message:
- throw new IllegalArgumentException("Project name in URL and request body do not match."); + throw new IllegalArgumentException( + String.format("Project name mismatch: '%s' in URL does not match '%s' in request body.", + projectName, topicResource.getProject()) + );server/src/test/java/com/flipkart/varadhi/web/admin/TopicHandlersTest.java (2)
169-183
: Add test coverage for error scenarios.Consider adding tests for:
- Non-existent topic
- Invalid topic name format
- Topic in deleted state
Would you like me to provide the implementation for these test cases?
231-238
: Add test coverage for restore error scenarios.Consider adding tests for:
- Restoring a non-existent topic
- Restoring an active topic
- Restoring a hard-deleted topic
Would you like me to provide the implementation for these test cases?
server/src/test/java/com/flipkart/varadhi/services/SubscriptionServiceTest.java (1)
Line range hint
349-365
: Enhance test helpers and mock setup for deletion scenarios.To improve test maintainability and reduce code duplication for the new deletion scenarios, consider:
- Adding a helper method for deletion operations:
private CompletableFuture<Void> deleteSubscription(String name, ResourceDeletionType deletionType) { return subscriptionService.deleteSubscription( name, o1t1p1, requestedBy, deletionType); }
- Enhancing mock setup to verify deletion type handling:
@Test void verifyDeletionTypeHandling() { // Setup doReturn(unGroupedTopic).when(varadhiMetaStore).getTopic(unGroupedTopic.getName()); subscriptionService.createSubscription(unGroupedTopic, sub1, o1t1p1); // Verify that deletion type is correctly passed to the meta store deleteSubscription(sub1.getName(), ResourceDeletionType.SOFT_DELETE); verify(varadhiMetaStore).markSubscriptionAsDeleted( eq(sub1.getName()), eq(ResourceDeletionType.SOFT_DELETE)); }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (17)
common/src/main/java/com/flipkart/varadhi/Constants.java
(3 hunks)entities/src/main/java/com/flipkart/varadhi/entities/ResourceDeletionType.java
(1 hunks)entities/src/main/java/com/flipkart/varadhi/entities/VaradhiSubscription.java
(4 hunks)entities/src/main/java/com/flipkart/varadhi/entities/VaradhiTopic.java
(1 hunks)entities/src/test/java/com/flipkart/varadhi/entities/VaradhiTopicTest.java
(1 hunks)producer/src/test/java/com/flipkart/varadhi/services/ProducerServiceTests.java
(1 hunks)server/src/main/java/com/flipkart/varadhi/config/RestOptions.java
(1 hunks)server/src/main/java/com/flipkart/varadhi/db/VaradhiMetaStore.java
(1 hunks)server/src/main/java/com/flipkart/varadhi/services/SubscriptionService.java
(2 hunks)server/src/main/java/com/flipkart/varadhi/services/VaradhiTopicService.java
(1 hunks)server/src/main/java/com/flipkart/varadhi/web/v1/admin/SubscriptionHandlers.java
(4 hunks)server/src/main/java/com/flipkart/varadhi/web/v1/admin/TopicHandlers.java
(4 hunks)server/src/test/java/com/flipkart/varadhi/services/SubscriptionServiceTest.java
(1 hunks)server/src/test/java/com/flipkart/varadhi/services/VaradhiTopicServiceTest.java
(2 hunks)server/src/test/java/com/flipkart/varadhi/web/admin/SubscriptionHandlersTest.java
(1 hunks)server/src/test/java/com/flipkart/varadhi/web/admin/TopicHandlersTest.java
(2 hunks)spi/src/main/java/com/flipkart/varadhi/spi/db/MetaStore.java
(1 hunks)
✅ Files skipped from review due to trivial changes (2)
- producer/src/test/java/com/flipkart/varadhi/services/ProducerServiceTests.java
- server/src/main/java/com/flipkart/varadhi/config/RestOptions.java
⏰ Context from checks skipped due to timeout of 90000ms (2)
- GitHub Check: call-workflow-docker-build / build
- GitHub Check: build
🔇 Additional comments (79)
entities/src/main/java/com/flipkart/varadhi/entities/VaradhiSubscription.java (19)
9-11
: Good documentation block.
No issues found with this introductory subscription description.
28-29
: Constants naming is clear.
Defining these error messages as constants improves readability.
31-46
: Comprehensive constructor documentation.
The parameter descriptions are clear and informative.
62-63
: Well-structured constructor validations.
Using helper methods to validate fields ensures consistency with minimal duplication.Also applies to: 69-71
74-89
: Factory method documentation is clear.
The static creation pattern is well-defined and self-explanatory.
104-104
: Initial status assignment.
Automatically setting the status to CREATING aligns with a typical creation workflow.
108-116
: Confirm the definition of "well-provisioned."
Currently, only the CREATED state is considered well-provisioned. Verify whether additional states should also be regarded as well-provisioned.
118-125
: Clarify the meaning of "active."
Right now, all states except INACTIVE are treated as active. Confirm if states like CREATE_FAILED or DELETE_FAILED should also be non-active.
128-134
: Creation failure handling.
Marking the subscription as CREATE_FAILED with a specific message is a robust approach to surface details.
137-141
: “Created” status transition.
Straightforward status update, keeping the code more readable.
144-150
: Delete failure handling looks good.
Clean usage of the shared update flow to manage failure messages.
153-158
: “Deleting” status transition.
This matches the typical lifecycle. No issues noted.
160-164
: Inactive status transition.
Explicitly marking the subscription as INACTIVE is consistent with soft-deletion semantics.
192-207
: Validation method is properly encapsulated.
This reduces duplication across the class.
209-223
: Shards validation.
This logic is correct for ensuring a nonzero shard count.
225-239
: Properties validation.
Properly handles empty or null properties, ensuring consistent usage.
241-249
: Update status concurrency check.
If multiple threads can modify the subscription simultaneously, concurrency considerations may be necessary. Otherwise, this is fine.
252-261
: State enum definitions.
Naming is intuitive, supporting typical lifecycle transitions.
264-273
: Status class usage.
Well-structured representation for storing both the state and accompanying message.common/src/main/java/com/flipkart/varadhi/Constants.java (2)
32-46
: LGTM! Well-structured constants class.The PathParams class follows best practices with:
- Clear documentation
- Logical grouping of related constants
- Prevention of instantiation
73-73
: Verify impact of TAG_REMOTE_HOST renaming.The renaming from TAG_REMOTEHOST to TAG_REMOTE_HOST improves naming consistency but might affect existing code.
server/src/test/java/com/flipkart/varadhi/services/VaradhiTopicServiceTest.java (4)
280-308
: LGTM!The project-specific topic tests are well-structured and thoroughly cover both active and inactive topic scenarios.
79-120
: Add test for duplicate topic creation.The creation tests cover successful creation and various failure scenarios, but there's no test for attempting to create a topic that already exists. This is an important edge case to verify.
Consider adding a test like:
@Test void createVaradhiTopic_TopicAlreadyExists_ThrowsException() { VaradhiTopic varadhiTopic = createVaradhiTopicMock(); when(metaStore.checkTopicExists(varadhiTopic.getName())).thenReturn(true); assertThrows(ResourceAlreadyExistsException.class, () -> varadhiTopicService.create(varadhiTopic, project)); verify(storageTopicService, never()).create(any(), any()); verify(metaStore, never()).createTopic(any()); }
207-231
: Add test for restoring a hard-deleted topic.The restoration tests don't cover the scenario of attempting to restore a topic that has been hard-deleted. This edge case should be verified to ensure proper error handling.
Consider adding a test like:
@Test void restoreVaradhiTopic_HardDeletedTopic_ThrowsException() { String topicName = "default.testTopic"; when(metaStore.getTopic(topicName)).thenThrow(new ResourceNotFoundException("Topic not found")); Exception exception = assertThrows( ResourceNotFoundException.class, () -> varadhiTopicService.restore(topicName) ); assertEquals("Topic not found", exception.getMessage()); verify(metaStore, never()).updateTopic(any()); }
255-278
: Add test for retrieving a soft-deleted topic.The retrieval tests should verify that soft-deleted topics can still be retrieved but are marked as inactive. This is important for the soft deletion feature.
Consider adding a test like:
@Test void getVaradhiTopic_SoftDeletedTopic_ReturnsInactiveTopic() { VaradhiTopic varadhiTopic = createVaradhiTopicMock(); varadhiTopic.updateStatus(VaradhiTopic.Status.INACTIVE); when(metaStore.getTopic(varadhiTopic.getName())).thenReturn(varadhiTopic); VaradhiTopic retrievedTopic = varadhiTopicService.get(varadhiTopic.getName()); assertNotNull(retrievedTopic); assertFalse(retrievedTopic.isActive()); }✅ Verification successful
Review comment is correct - test for retrieving soft-deleted topics is needed
The codebase shows that VaradhiTopic supports active/inactive states through its Status enum, and while the entity layer has good test coverage for status changes, the service layer (VaradhiTopicServiceTest) lacks tests for retrieving topics in different states. The suggested test would improve coverage of this important functionality.
The proposed test in the review comment is well-structured and correctly uses:
- Status.INACTIVE for marking the topic as soft-deleted
- Proper assertions to verify the inactive state
- Consistent style with existing tests
🏁 Scripts executed
The following scripts were executed for the analysis:
Error: No shell scripts found
Script:
#!/bin/bash # Search for VaradhiTopic class definition and Status enum ast-grep --pattern 'class VaradhiTopic { $$$ }' # Search for any existing soft deletion related tests rg -l "soft.*delet|inactive|status" --type java # Search for status-related methods in VaradhiTopicService ast-grep --pattern 'class VaradhiTopicService { $$$ }'Length of output: 2735
Script:
#!/bin/bash # Check VaradhiTopic implementation echo "=== VaradhiTopic.java ===" cat entities/src/main/java/com/flipkart/varadhi/entities/VaradhiTopic.java echo -e "\n=== TopicState.java ===" cat entities/src/main/java/com/flipkart/varadhi/entities/TopicState.java echo -e "\n=== VaradhiTopicTest.java ===" cat entities/src/test/java/com/flipkart/varadhi/entities/VaradhiTopicTest.javaLength of output: 10319
server/src/main/java/com/flipkart/varadhi/services/VaradhiTopicService.java (5)
4-8
: Imports introduced for resource and exception handling
These imports (ResourceDeletionType, StorageTopic, VaradhiTopic, and exception classes) correctly reflect the new deletion logic and exception usage.
21-21
: Well-defined final fields
DeclaringstorageTopicService
andmetaStore
as final is good practice to ensure immutability.
24-31
: Constructor JavaDocs
The constructor documentation is clear and aligns well with the parameters.
72-86
: Deletion Type logic
Soft vs. hard deletion logic is separated cleanly. The TODO for namespace/tenant cleanup might need follow-up to avoid confusion.
157-176
: Existence check & topic retrieval
Theexists
andgetVaradhiTopics
methods are clear. Filtering out inactive topics helps maintain a clean API.server/src/main/java/com/flipkart/varadhi/services/SubscriptionService.java (11)
7-7
: ResourceNotFoundException import
This import aligns well with usage throughout the class for invalid/missing subscriptions.
14-14
: BiFunction usage
Neat approach passing subscription operations as a BiFunction, promoting clean invocation.
16-18
: Class-level Javadoc
Documentation gives a good overview of subscription service responsibilities and new exception handling.
25-35
: Constructor clarity
The Javadoc thoroughly describes each constructor parameter.
41-51
: Listing subscriptions
Filtering byisActiveOrWellProvisionedByName
is intuitive for returning valid subscriptions.
54-61
: Retrieval approach
The method delegates togetValidatedSubscription
for streamlined error handling.
93-112
: Start/Stop subscription
Delegating toperformSubscriptionOperation
is effective for code reuse and consistency.
188-211
: Restore subscription
Restores an inactive subscription. The flow is simple, and the log statement is helpful.
213-233
: State checks
isActiveOrWellProvisioned
helps unify subscription readiness checks.
253-266
: Grouped subscription check
Enforces synergy between grouped topics and grouped subscriptions.
268-276
: Version update check
Throws exception on version mismatch to prevent inconsistent updates. Solid concurrency safeguard.server/src/main/java/com/flipkart/varadhi/web/v1/admin/SubscriptionHandlers.java (17)
5-8
: Reintroduced VaradhiTopicService and ResourceType imports
These imports restore the handling of topics and resource authorization.
25-29
: New query param constants
UsingQUERY_PARAM_DELETION_TYPE
andQUERY_PARAM_IGNORE_CONSTRAINTS
standardizes these features across endpoints.
36-39
: Class Javadoc
Describes the subscription operations comprehensively, including delete/restore functionality.
43-61
: Constructor
Instantiating property validators and default value providers up front is clean.
Line range hint
78-120
: Route definitions
AddingRestoreSubscription
route aligns well with soft deletion. Overall route structure remains consistent.
192-196
: Get endpoint
Creates aSubscriptionResource
from a validated subscription. Straightforward flow.
222-241
: Update endpoint
TheTODO
mentions splitting into separate update operations. Carefully evaluate partial updates to avoid unintended changes.
244-262
: Delete endpoint
Defaults to soft deletion if not specified. Safer default choice.
265-273
: Restore endpoint
Complements soft deletion. The REST route is consistent with the existing pattern.
275-291
: Start/Stop endpoints
Delegation tosubscriptionService
is straightforward. Ensures consistent lifecycle management in service logic.
293-304
: Building subscription FQN
A concise, well-defined helper method.
307-322
: Validating subscription resource
Multiple validations in a single method. Centralizes error checking well.
326-335
: Super user constraints
RestrictingignoreConstraints
to super admins prevents abuse.
338-347
: Retry policy validation
Restricting to a specific retry count can simplify operational assumptions.
350-360
: Project consistency check
Ensures the path-param project matches the request body project, preventing misconfiguration.
362-386
: Get subscribed topic
Constructs the FQN for the topic and retrieves it. Straightforward approach.
388-418
: Property validation
Ensures unknown properties are rejected, sets defaults, and validates syntax. This is robust and user-friendly.server/src/main/java/com/flipkart/varadhi/db/VaradhiMetaStore.java (1)
193-197
: LGTM!The implementation follows the established pattern in the class and correctly updates the topic data in the ZK meta store.
server/src/main/java/com/flipkart/varadhi/web/v1/admin/TopicHandlers.java (13)
3-6
: LGTM!The new imports are correctly organized and necessary for the soft-deletion functionality.
35-38
: LGTM!The class-level Javadoc is clear and follows standard format.
46-52
: LGTM!The constructor Javadoc is well-documented with clear parameter descriptions.
Line range hint
63-92
: LGTM!The route definitions are well-organized and the new restoration route follows the established pattern.
94-102
: LGTM!The method rename improves clarity and the implementation is correct.
104-127
: LGTM!The method correctly handles resource hierarchies for both request types.
129-137
: LGTM!The method is well-documented and correctly implements topic retrieval.
145-146
: Address TODO comments.Consider implementing:
- Vertx ValidationHandlers for request body validation
- Rollback mechanisms for failure scenarios
Would you like me to help implement these improvements?
164-177
: LGTM!The method correctly implements deletion type handling with a sensible default to soft deletion.
179-187
: LGTM!The restore method is well-documented and correctly implements topic restoration.
189-201
: LGTM!The method efficiently uses streams for topic filtering and mapping.
204-215
: LGTM!The method is well-documented and correctly uses the helper method for name construction.
231-240
: LGTM!The method is well-documented and correctly implements topic name construction.
server/src/test/java/com/flipkart/varadhi/web/admin/TopicHandlersTest.java (6)
44-82
: LGTM!The test class setup is well-organized with proper use of Mockito annotations and constants.
84-111
: LGTM!The route setup is comprehensive and well-organized.
123-166
: LGTM!The create topic tests provide good coverage of success and error scenarios.
186-208
: LGTM!The list topics tests provide good coverage of different scenarios.
212-229
: LGTM!The delete topic tests provide excellent coverage of all deletion scenarios.
241-270
: LGTM!The helper methods are well-organized and promote code reuse.
server/src/test/java/com/flipkart/varadhi/services/SubscriptionServiceTest.java (1)
350-351
: Add test coverage for soft deletion and restoration.While the hard deletion test case is implemented, comprehensive test coverage is needed for the new soft deletion functionality. Consider adding the following test cases:
- Soft deletion of a subscription
- Restoration of a soft-deleted subscription
- Edge cases:
- Attempting to restore a hard-deleted subscription
- Attempting to hard delete an already soft-deleted subscription
- Attempting to soft delete an already soft-deleted subscription
✅ Verification successful
Add test coverage for soft deletion and restoration functionality
The codebase has proper implementation of soft deletion and restoration features in
SubscriptionService
, butSubscriptionServiceTest
lacks test coverage for these scenarios. Following the patterns established inVaradhiTopicServiceTest
, add test cases for:
- Soft deletion of a subscription
- Successful restoration of a soft-deleted subscription
- Edge cases:
- Attempting to restore an active subscription
- Attempting to restore a hard-deleted subscription
- Attempting to soft delete an already soft-deleted subscription
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Description: Check for existing test coverage of soft deletion and restoration. # Test: Search for test methods related to soft deletion and restoration rg -t java "test.*(?i)(soft.*delete|restore)" "server/src/test" # Test: Search for usage of SOFT_DELETE enum value in tests rg -t java "SOFT_DELETE" "server/src/test"Length of output: 762
Script:
#!/bin/bash # Search for restore/undelete related methods and endpoints rg -t java "(?i)(restore|undelete)" "server/src" # Search for method signatures in SubscriptionService ast-grep --pattern 'interface SubscriptionService { $$$ }'Length of output: 4499
// TODO: This header is intended for testing purposes only. The "x_" prefix may result in it being sent to the destination during consumption. | ||
// Header for user ID, intended for testing purposes only |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Address security concern in USER_ID_HEADER.
The TODO comment indicates a potential security risk where the "x_" prefixed header might be forwarded to the destination during consumption. This could lead to information leakage.
Consider:
- Using a different prefix that won't be forwarded
- Implementing header filtering before forwarding
- Documenting this limitation in security documentation
@Test | ||
public void deleteVaradhiTopicSuccessfully() { | ||
TopicResource topicResource = getTopicResource(topicName, project); | ||
VaradhiTopic varadhiTopic = varadhiTopicFactory.get(project, topicResource); | ||
StorageTopic st = varadhiTopic.getProduceTopicForRegion(region).getTopicToProduce(); | ||
when(storageTopicService.exists(st.getName())).thenReturn(true); | ||
when(metaStore.getTopic(varadhiTopic.getName())).thenReturn(varadhiTopic); | ||
when(metaStore.getProject(project.getName())).thenReturn(project); | ||
void deleteVaradhiTopic_SuccessfulHardDelete() { | ||
VaradhiTopic varadhiTopic = mockDeleteSetup(); | ||
|
||
varadhiTopicService.delete(varadhiTopic.getName()); | ||
varadhiTopicService.delete(varadhiTopic.getName(), ResourceDeletionType.HARD_DELETE); | ||
|
||
verify(storageTopicService, times(1)).delete(st.getName(), project); | ||
verify(storageTopicService, times(1)).delete(pulsarStorageTopic.getName(), project); | ||
verify(metaStore, times(1)).deleteTopic(varadhiTopic.getName()); | ||
} | ||
|
||
@Test | ||
public void deleteVaradhiTopicWhenStorageTopicDoesNotExist() { | ||
TopicResource topicResource = getTopicResource(topicName, project); | ||
VaradhiTopic varadhiTopic = varadhiTopicFactory.get(project, topicResource); | ||
StorageTopic st = varadhiTopic.getProduceTopicForRegion(region).getTopicToProduce(); | ||
when(storageTopicService.exists(st.getName())).thenReturn(false); | ||
void deleteVaradhiTopic_StorageTopicDoesNotExist_SuccessfulHardDelete() { | ||
VaradhiTopic varadhiTopic = createVaradhiTopicMock(); | ||
when(storageTopicService.exists(pulsarStorageTopic.getName())).thenReturn(false); | ||
when(metaStore.getTopic(varadhiTopic.getName())).thenReturn(varadhiTopic); | ||
when(metaStore.getProject(project.getName())).thenReturn(project); | ||
varadhiTopicService.delete(varadhiTopic.getName()); | ||
verify(storageTopicService, times(1)).delete(st.getName(), project); | ||
|
||
varadhiTopicService.delete(varadhiTopic.getName(), ResourceDeletionType.HARD_DELETE); | ||
|
||
verify(storageTopicService, times(1)).delete(pulsarStorageTopic.getName(), project); | ||
verify(metaStore, times(1)).deleteTopic(varadhiTopic.getName()); | ||
} | ||
|
||
@Test | ||
public void deleteVaradhiTopicWhenMetaStoreFails() { | ||
TopicResource topicResource = getTopicResource(topicName, project); | ||
VaradhiTopic varadhiTopic = varadhiTopicFactory.get(project, topicResource); | ||
StorageTopic st = varadhiTopic.getProduceTopicForRegion(region).getTopicToProduce(); | ||
when(storageTopicService.exists(st.getName())).thenReturn(true); | ||
when(metaStore.getTopic(varadhiTopic.getName())).thenReturn(varadhiTopic); | ||
when(metaStore.getProject(project.getName())).thenReturn(project); | ||
doThrow(new VaradhiException("Some error")).when(metaStore).deleteTopic(varadhiTopic.getName()); | ||
void deleteVaradhiTopic_MetaStoreFailure_ThrowsException() { | ||
VaradhiTopic varadhiTopic = mockDeleteSetup(); | ||
doThrow(new VaradhiException("MetaStore deletion failed")).when(metaStore).deleteTopic(varadhiTopic.getName()); | ||
|
||
Exception exception = Assertions.assertThrows( | ||
Exception exception = assertThrows( | ||
VaradhiException.class, | ||
() -> varadhiTopicService.delete(varadhiTopic.getName()) | ||
() -> varadhiTopicService.delete(varadhiTopic.getName(), ResourceDeletionType.HARD_DELETE) | ||
); | ||
|
||
verify(storageTopicService, times(1)).delete(st.getName(), project); | ||
verify(storageTopicService, times(1)).delete(pulsarStorageTopic.getName(), project); | ||
verify(metaStore, times(1)).deleteTopic(varadhiTopic.getName()); | ||
Assertions.assertEquals(exception.getClass(), VaradhiException.class); | ||
assertEquals(VaradhiException.class, exception.getClass()); | ||
assertEquals("MetaStore deletion failed", exception.getMessage()); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Codebase verification
The review comment is correct - test coverage for hard deleting a soft-deleted topic is missing
The suggested test is important because:
- VaradhiTopic supports INACTIVE status for soft-deleted topics
- The service implementation allows hard deleting soft-deleted topics
- This state transition is not covered by existing tests
The suggested test implementation is appropriate and matches the service's behavior.
🔗 Analysis chain
Add test for hard deleting a soft-deleted topic.
The deletion tests don't cover the scenario of attempting to hard delete a topic that's already soft-deleted. This is an important state transition to verify.
Consider adding a test like:
@Test
void deleteVaradhiTopic_HardDeleteSoftDeletedTopic_SuccessfulDelete() {
VaradhiTopic varadhiTopic = mockDeleteSetup();
varadhiTopic.updateStatus(VaradhiTopic.Status.INACTIVE);
varadhiTopicService.delete(varadhiTopic.getName(), ResourceDeletionType.HARD_DELETE);
verify(storageTopicService, times(1)).delete(pulsarStorageTopic.getName(), project);
verify(metaStore, times(1)).deleteTopic(varadhiTopic.getName());
}
🏁 Scripts executed
The following scripts were executed for the analysis:
Error: No shell scripts found
Script:
#!/bin/bash
# Search for all test methods related to topic deletion
echo "=== Searching for deletion-related tests ==="
rg "void.*delete.*Topic.*\{" -g "**/*Test.java" -A 2
echo -e "\n=== Looking for VaradhiTopic class implementation ==="
ast-grep --pattern 'class VaradhiTopic {
$$$
}'
echo -e "\n=== Searching for Status enum in VaradhiTopic ==="
rg "enum.*Status" -g "**/VaradhiTopic.java" -A 5
Length of output: 4290
Script:
#!/bin/bash
# Look for VaradhiTopicService implementation of delete method
echo "=== Searching for delete implementation in VaradhiTopicService ==="
ast-grep --pattern 'class VaradhiTopicService {
$$$
delete($$$) {
$$$
}
$$$
}'
# Look for any status-related checks in delete implementation
echo -e "\n=== Searching for status checks in delete method ==="
rg "delete.*\{" -g "**/VaradhiTopicService.java" -A 20
Length of output: 2934
/** | ||
* Creates a new Varadhi topic. | ||
* | ||
* @param varadhiTopic the Varadhi topic to create | ||
* @param project the project associated with the topic | ||
*/ | ||
public void create(VaradhiTopic varadhiTopic, Project project) { | ||
log.info("Creating Varadhi topic {}", varadhiTopic.getName()); | ||
// StorageTopicService.create() to ensure if pre-existing topic can be re-used. | ||
// i.e. topic creation at storage level need to be idempotent. | ||
varadhiTopic.getInternalTopics().forEach((region, internalTopic) -> internalTopic.getActiveTopics() | ||
.forEach(storageTopic -> topicService.create(storageTopic, project))); | ||
log.info("Creating Varadhi topic: {}", varadhiTopic.getName()); | ||
// Ensure StorageTopicService.create() is idempotent, allowing reuse of pre-existing topics. | ||
varadhiTopic.getInternalTopics().forEach((region, internalTopic) -> | ||
internalTopic.getActiveTopics().forEach(storageTopic -> | ||
storageTopicService.create(storageTopic, project) | ||
) | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Create method partial failure handling
If storageTopicService.create(...)
succeeds but metaStore.createTopic(...)
fails, resources may become partially created. Consider a compensating strategy or transaction-like approach.
/** | ||
* Handles the hard deletion of a Varadhi topic. | ||
* | ||
* @param varadhiTopic the Varadhi topic to hard delete | ||
*/ | ||
public void handleHardDelete(VaradhiTopic varadhiTopic) { | ||
log.info("Hard deleting Varadhi topic: {}", varadhiTopic.getName()); | ||
|
||
Project project = metaStore.getProject(varadhiTopic.getProjectName()); | ||
|
||
varadhiTopic.getInternalTopics().forEach((region, internalTopic) -> | ||
internalTopic.getActiveTopics().forEach(storageTopic -> | ||
storageTopicService.delete(storageTopic.getName(), project) | ||
) | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Hard delete partial failures
If storageTopicService.delete(...)
is successful for some regions but fails for others, leftover resources might remain. Investigate a rollback or error-handling mechanism.
/** | ||
* Handles the hard deletion of a subscription. | ||
* | ||
* @param subscription the subscription to delete | ||
* @param subProject the project associated with the subscription | ||
*/ | ||
private void handleHardDelete(VaradhiSubscription subscription, Project subProject) { | ||
subscription.markDeleting(); | ||
metaStore.updateSubscription(subscription); | ||
|
||
try { | ||
shardProvisioner.deProvision(subscription, subProject); | ||
metaStore.deleteSubscription(subscription.getName()); | ||
log.info("Subscription '{}' deleted successfully.", subscription.getName()); | ||
} catch (Exception e) { | ||
log.error("Failed to hard delete subscription '{}'.", subscription.getName(), e); | ||
subscription.markDeleteFailed(e.getMessage()); | ||
metaStore.updateSubscription(subscription); | ||
try { | ||
shardProvisioner.deProvision(subscription, subProject); | ||
metaStore.deleteSubscription(subscriptionName); | ||
} catch (Exception e) { | ||
log.error("Delete failed.", e); | ||
subscription.markDeleteFailed(e.getMessage()); | ||
metaStore.updateSubscription(subscription); | ||
throw e; | ||
} | ||
}); | ||
throw e; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
handleHardDelete
If deProvision
partially fails, the subscription might remain partially deleted. Consider transaction or repeated attempts.
- Overloaded the `makeDeleteRequest` and `makeHttpDeleteRequest` method in `E2EBase` to accept a deletion type parameter. - Updated cleanup methods to use hard delete to ensure resources are fully removed. - Ensured that resources are hard deleted during cleanup to prevent conflicts when creating new ones with the same name. - Corrected exception messages in tests to match the expected responses. - Added `@JsonIgnore` annotation on `isActive` field in `VaradhiTopic` entity to prevent it from being serialized.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (6)
server/src/testE2E/java/com/flipkart/varadhi/SubscriptionTests.java (1)
125-125
: Review test cleanup strategy.The test cleanup operations consistently use hard delete. Consider documenting this decision in a test utility class or as a comment to explain why hard delete is preferred for test cleanup.
entities/src/main/java/com/flipkart/varadhi/entities/VaradhiTopic.java (3)
20-41
: Add null check in constructor for status parameter.While the
updateStatus
method includes null validation, the constructor should also validate the status parameter to maintain consistency and prevent null states.private VaradhiTopic( String name, int version, boolean grouped, TopicCapacityPolicy capacity, Map<String, InternalCompositeTopic> internalTopics, Status status ) { super(name, version); + if (status == null) { + throw new IllegalArgumentException("Status cannot be null"); + } this.grouped = grouped; this.capacity = capacity; this.internalTopics = internalTopics; this.status = status; }
43-54
: Enhance factory method documentation.The documentation should mention that the topic is created with a default
Status.ACTIVE
state./** * Creates a new VaradhiTopic instance. * * @param project the project associated with the topic * @param name the name of the topic * @param grouped whether the topic is grouped * @param capacity the capacity policy of the topic + * @return a new VaradhiTopic instance with Status.ACTIVE state */
122-128
: Consider enhancing Status enum for better lifecycle management.Given that this implements soft-deletion support, consider:
- Adding a
PENDING_DELETION
state to support graceful deletion process- Implementing state transition validation to prevent invalid state changes
Example enhancement:
public enum Status { ACTIVE, PENDING_DELETION, // Intermediate state during deletion INACTIVE; // Soft-deleted state public boolean canTransitionTo(Status newStatus) { return switch (this) { case ACTIVE -> newStatus == PENDING_DELETION; case PENDING_DELETION -> newStatus == INACTIVE; case INACTIVE -> newStatus == ACTIVE; // Support restoration }; } }Then update the
updateStatus
method to use this validation:public void updateStatus(Status newStatus) { if (newStatus == null) { throw new IllegalArgumentException("Status cannot be null"); } if (!this.status.canTransitionTo(newStatus)) { throw new IllegalStateException( "Invalid state transition from " + this.status + " to " + newStatus); } this.status = newStatus; }server/src/testE2E/java/com/flipkart/varadhi/E2EBase.java (2)
137-137
: Extract "HARD_DELETE" as a constant.The string literal "HARD_DELETE" is used in multiple places. Consider extracting it as a class constant to improve maintainability and prevent typos.
+ private static final String HARD_DELETE = "HARD_DELETE"; - makeDeleteRequest(getTopicsUri(project, topicName), "HARD_DELETE", 200); + makeDeleteRequest(getTopicsUri(project, topicName), HARD_DELETE, 200);
166-167
: Improve readability of subscription name parsing.
- This is another occurrence of the "HARD_DELETE" string literal that should use the suggested constant.
- The subscription name parsing logic using split makes the code less readable.
Consider extracting the subscription name parsing:
+ private static String parseSubscriptionName(String fullName) { + return fullName.split(NAME_SEPARATOR_REGEX)[1]; + } static void cleanupSubscriptionsOnProject(Project project) { getSubscriptions(makeListRequest(getSubscriptionsUri(project), 200)).forEach( - s -> makeDeleteRequest( - getSubscriptionsUri(project, s.split(NAME_SEPARATOR_REGEX)[1]), "HARD_DELETE", 200)); + s -> makeDeleteRequest( + getSubscriptionsUri(project, parseSubscriptionName(s)), HARD_DELETE, 200)); }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
entities/src/main/java/com/flipkart/varadhi/entities/VaradhiTopic.java
(1 hunks)server/src/test/java/com/flipkart/varadhi/services/SubscriptionServiceTest.java
(4 hunks)server/src/test/java/com/flipkart/varadhi/web/admin/SubscriptionHandlersTest.java
(2 hunks)server/src/testE2E/java/com/flipkart/varadhi/E2EBase.java
(5 hunks)server/src/testE2E/java/com/flipkart/varadhi/SubscriptionTests.java
(3 hunks)server/src/testE2E/java/com/flipkart/varadhi/TopicTests.java
(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- server/src/test/java/com/flipkart/varadhi/web/admin/SubscriptionHandlersTest.java
⏰ Context from checks skipped due to timeout of 90000ms (2)
- GitHub Check: call-workflow-docker-build / build
- GitHub Check: build
🔇 Additional comments (12)
server/src/testE2E/java/com/flipkart/varadhi/SubscriptionTests.java (3)
84-84
: Consider adding test coverage for soft deletion.While using hard delete for cleanup is valid, this test class should include coverage for both soft and hard deletion scenarios to validate the new functionality completely.
Would you like me to help generate additional test cases to cover soft deletion flows?
195-195
: LGTM! Improved error message clarity.The updated error message is more descriptive and clearly indicates that the grouped subscription constraint applies to both creation and updates.
Line range hint
1-197
: Add comprehensive test coverage for deletion functionality.While the changes are correct, this test class would benefit from additional test coverage for the new deletion functionality:
- Test soft deletion of a subscription
- Test restoration of a soft-deleted subscription
- Test operations on soft-deleted subscriptions (should fail)
- Test hard deletion after soft deletion
- Test various edge cases around deletion states
Would you like me to help generate these additional test cases?
entities/src/main/java/com/flipkart/varadhi/entities/VaradhiTopic.java (1)
98-120
: Well-implemented status management methods.The implementation includes proper validation, documentation, and appropriate use of @JsonIgnore annotation.
server/src/testE2E/java/com/flipkart/varadhi/E2EBase.java (2)
156-156
: Same string literal "HARD_DELETE" used.This is another occurrence of the string literal that should use the suggested constant.
255-258
: Extract query parameter name as constant and verify parameter validation.
- The query parameter name "deletionType" should be extracted as a constant for consistency.
- We should verify that the deletionType parameter is properly validated in the endpoint handler.
+ private static final String DELETION_TYPE_PARAM = "deletionType"; static Response makeHttpDeleteRequest(String targetUrl, String deletionType) { return getClient() .target(targetUrl) - .queryParam("deletionType", deletionType) + .queryParam(DELETION_TYPE_PARAM, deletionType) .request(MediaType.APPLICATION_JSON_TYPE) .header(USER_ID_HEADER, SUPER_USER) .delete(); }Let's verify the parameter validation in the endpoint handler:
Also applies to: 302-309
server/src/testE2E/java/com/flipkart/varadhi/TopicTests.java (2)
62-64
: LGTM! Improved error message format.The simplified error message format is more user-friendly while maintaining clarity.
67-67
: 🛠️ Refactor suggestionAdd test coverage for soft deletion scenarios.
While the hard deletion tests are in place, there are several missing test scenarios that should be covered:
- Soft deletion of topics
- Restoration of soft-deleted topics
- Validation of invalid deletion types
- Error scenarios in deletion flows (e.g., deleting already deleted topics)
Here's a suggested test structure:
@Test public void testTopicDeletionScenarios() { String topicName = "test-topic-deletion"; TopicResource topic = TopicResource.unGrouped(topicName, o1t1Project1.getName(), null); // Create topic makeCreateRequest(getTopicsUri(o1t1Project1), topic, 200); // Test soft deletion makeDeleteRequest(getTopicsUri(o1t1Project1, topicName), "SOFT_DELETE", 200); // Verify topic is soft deleted but still exists TopicResource deletedTopic = makeGetRequest( getTopicsUri(o1t1Project1, topicName), TopicResource.class, 200 ); Assertions.assertTrue(deletedTopic.isDeleted()); // Test restoration makeRestoreRequest(getTopicsUri(o1t1Project1, topicName), 200); TopicResource restoredTopic = makeGetRequest( getTopicsUri(o1t1Project1, topicName), TopicResource.class, 200 ); Assertions.assertFalse(restoredTopic.isDeleted()); // Test invalid deletion type makeDeleteRequest( getTopicsUri(o1t1Project1, topicName), "INVALID_DELETE", 400, "Invalid deletion type specified", true ); // Clean up makeDeleteRequest(getTopicsUri(o1t1Project1, topicName), "HARD_DELETE", 200); }Also applies to: 98-99
server/src/test/java/com/flipkart/varadhi/services/SubscriptionServiceTest.java (4)
239-240
: LGTM! Improved error message clarity.The updated error message is more descriptive and accurately reflects both creation and update scenarios.
298-298
: LGTM! Enhanced version conflict message.The updated error message is more professional and provides clear guidance on the next steps.
325-326
: LGTM! Consistent error messaging.The error message maintains consistency with the creation scenario, helping users understand that the same rules apply to both operations.
352-353
: Add test coverage for soft deletion.While the hard deletion test case is implemented, we should also add test coverage for soft deletion to ensure both deletion types work as expected. This aligns with the PR objectives of implementing comprehensive soft deletion support.
Consider adding the following test cases:
- Soft deletion marks the subscription as deleted but keeps it retrievable
- Restoration of a soft-deleted subscription
- Verify that operations on soft-deleted subscriptions are properly restricted
+ @Test + void softDeleteSubscriptionMarksAsDeleted(VertxTestContext ctx) { + Checkpoint checkpoint = ctx.checkpoint(1); + doReturn(unGroupedTopic).when(varadhiMetaStore).getTopic(unGroupedTopic.getName()); + subscriptionService.createSubscription(unGroupedTopic, sub1, o1t1p1); + + String name = sub1.getName(); + CompletableFuture<SubscriptionState> status = + CompletableFuture.completedFuture(SubscriptionState.forStopped()); + doReturn(status).when(controllerRestApi).getSubscriptionState(name, requestedBy); + + Future.fromCompletionStage( + subscriptionService.deleteSubscription(name, o1t1p1, requestedBy, ResourceDeletionType.SOFT_DELETE)) + .onComplete(ctx.succeeding(v -> { + VaradhiSubscription subscription = subscriptionService.getSubscription(name); + assertTrue(subscription.isDeleted()); + checkpoint.flag(); + })); + }✅ Verification successful
Review comment is valid - soft deletion test coverage is missing
The codebase shows that soft deletion is implemented in
SubscriptionHandlers
(defaulting toSOFT_DELETE
) andSubscriptionService
supports both deletion types, but test coverage only exists for hard deletion. The suggested test cases would help ensure the soft deletion functionality works as expected.🏁 Scripts executed
The following scripts were executed for the analysis:
Error: No shell scripts found
Script:
#!/bin/bash # Search for soft deletion related tests and usage echo "=== Searching for soft deletion test methods ===" rg -l "test.*soft.*delete" --type java echo -e "\n=== Searching for SOFT_DELETE usage in tests ===" rg "SOFT_DELETE" --type java echo -e "\n=== Searching for soft deletion related assertions ===" rg "isDeleted|softDelete" --type javaLength of output: 1908
Script:
#!/bin/bash # Check subscription service implementation for soft deletion echo "=== Checking SubscriptionService implementation ===" ast-grep --pattern 'deleteSubscription($$$, $_, ResourceDeletionType $_) { $$$ }' echo -e "\n=== Checking for isDeleted method in subscription entity ===" ast-grep --pattern 'isDeleted() { $$$ }'Length of output: 380
Script:
#!/bin/bash # Search for subscription service and entity implementations echo "=== Searching for SubscriptionService class ===" rg -A 5 "class .*SubscriptionService" --type java echo -e "\n=== Searching for Subscription entity class ===" rg -A 5 "class .*Subscription.*implements" --type java echo -e "\n=== Searching for deletion related methods in subscription ===" rg "delete.*Subscription" --type javaLength of output: 9001
Test Results409 tests 409 ✅ 57s ⏱️ Results for commit 514abe4. ♻️ This comment has been updated with latest results. |
…ices - Added comprehensive unit tests for VaradhiSubscription, SubscriptionHandlers & SubscriptionService. - Refactored SubscriptionTestBase and WebTestBase for better readability and maintainability along with certain enhancements. - Enhanced tests for entity, handler, and service layers, covering all edge cases. - Ensured consistency with existing test styles and used appropriate mocking and stubbing libraries.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 6
🔭 Outside diff range comments (1)
server/src/test/java/com/flipkart/varadhi/web/produce/ProduceHandlersTest.java (1)
Line range hint
1-209
: Consider adding tests for soft-deletion scenarios.While the method renaming changes are consistent and maintain existing test coverage, consider adding the following test cases to support the PR's soft-deletion feature:
- Produce attempt to a soft-deleted topic
- Produce attempt during topic restoration
- Produce after successful topic restoration
This will ensure robust validation of the new soft-deletion functionality.
🧹 Nitpick comments (30)
server/src/test/java/com/flipkart/varadhi/web/produce/BodyHandlerTest.java (2)
64-67
: Consider consolidating error test cases.Both test cases verify the same error condition (413 "Entity too large") with different oversized payloads (21 and 30 bytes). Consider using a parameterized test to improve test maintainability.
Example refactor:
- payload = "012345678901234567890".getBytes(); - sendRequestWithPayload( - request, payload, 413, "Entity too large.", - ErrorResponse.class - ); - - payload = "012345678901234567890123456789".getBytes(); - sendRequestWithPayload( - request, payload, 413, "Entity too large.", - ErrorResponse.class - ); + String[][] oversizedPayloads = { + {"012345678901234567890", "21 bytes"}, + {"012345678901234567890123456789", "30 bytes"} + }; + + for (String[] testCase : oversizedPayloads) { + payload = testCase[0].getBytes(); + sendRequestWithPayload( + request, payload, 413, "Entity too large.", + ErrorResponse.class + ); + }Also applies to: 70-73
Line range hint
51-74
: Enhance test method documentation.While the test coverage is comprehensive, consider adding clear documentation to describe the test scenarios:
- Success cases: 10 bytes (well below limit), 19 bytes (just below limit), 20 bytes (at limit)
- Error cases: 21 bytes (just above limit), 30 bytes (well above limit)
Example improvement:
+ /** + * Validates the body handler's size limit enforcement: + * - Success cases: Tests payloads of 10, 19, and 20 bytes (limit) + * - Error cases: Tests oversized payloads of 21 and 30 bytes + * Expected: 413 "Entity too large" for payloads exceeding 20 bytes + */ @Test - public void testProduceWithForBodySize() throws InterruptedException { + public void testProduceHandlerEnforcesSizeLimit() throws InterruptedException {server/src/test/java/com/flipkart/varadhi/web/produce/HeaderValidationTest.java (2)
114-115
: LGTM! Consider additional edge cases.The method name update maintains the test's validation of multi-value header counting. Consider adding test cases for:
- Empty multi-value headers
- Null values in multi-value headers
- Maximum size validation for combined multi-value header lengths
Line range hint
1-117
: Consider additional test coverage for soft-deletion scenarios.Given that this PR implements soft-deletion support, consider adding test cases to verify header validation behavior for:
- Requests to soft-deleted topics
- Restoration requests
- Hard deletion requests
This would ensure header validation remains consistent across all resource states.server/src/main/java/com/flipkart/varadhi/web/v1/admin/SubscriptionHandlers.java (1)
399-417
: Enhance error messages in property validation.While the validation logic is thorough, consider improving error messages to include:
- The invalid value that was provided
- The expected format or range
- Any additional context that would help users fix the issue
if (!validator.isValid(value, usePermissible)) { - throw new IllegalArgumentException("Invalid value for property: " + key); + throw new IllegalArgumentException(String.format( + "Invalid value '%s' for property '%s'. %s", + value, key, validator.getValidationDescription() + )); }server/src/test/java/com/flipkart/varadhi/web/admin/TopicHandlersTest.java (4)
46-49
: Consider using more descriptive constant names.The constant names could be more descriptive to better indicate their test context. For example,
TEST_TOPIC_NAME
,TEST_TEAM_NAME
, etc.- private static final String TOPIC_NAME = "topic1"; - private static final String TEAM_NAME = "team1"; - private static final String ORG_NAME = "org1"; - private static final String DEFAULT_PROJECT_NAME = "project1"; + private static final String TEST_TOPIC_NAME = "topic1"; + private static final String TEST_TEAM_NAME = "team1"; + private static final String TEST_ORG_NAME = "org1"; + private static final String TEST_PROJECT_NAME = "project1";
84-111
: Add documentation for route handlers.Consider adding Javadoc comments to document the purpose and behavior of each route handler. This would make it easier for other developers to understand the test setup.
+ /** + * Sets up all route handlers for topic operations including: + * - Create topic (POST /projects/:project/topics) + * - Get topic (GET /projects/:project/topics/:topic) + * - List topics (GET /projects/:project/topics) + * - Delete topic (DELETE /projects/:project/topics/:topic) + * - Restore topic (POST /projects/:project/topics/:topic/restore) + */ private void setupRoutes() {
212-229
: Add verification for topic status after soft deletion.The deletion tests verify the service call but don't verify the resulting topic status. Consider adding assertions to verify the topic's state after soft deletion.
@Test void deleteTopic_WithSoftDelete_ShouldDeleteTopicSuccessfully() throws InterruptedException { + TopicResource topicResource = getTopicResource(project); + VaradhiTopic varadhiTopic = topicResource.toVaradhiTopic(); + String varadhiTopicName = String.join(".", project.getName(), TOPIC_NAME); + + doReturn(varadhiTopic).when(varadhiTopicService).get(varadhiTopicName); verifyDeleteRequest("SOFT_DELETE", ResourceDeletionType.SOFT_DELETE); + + // Verify topic status after soft deletion + TopicResource retrievedTopic = sendRequestWithoutPayload( + createRequest(HttpMethod.GET, getTopicUrl(project)), + TopicResource.class + ); + assertEquals("DELETED", retrievedTopic.getStatus()); }
261-271
: Add input validation for URL construction helpers.The URL construction helpers don't validate their inputs. Consider adding null checks and validation for the project parameter.
private String getTopicsUrl(Project project) { + if (project == null || project.getName() == null) { + throw new IllegalArgumentException("Project and project name must not be null"); + } return String.join("/", "/projects", project.getName(), "topics"); } private String getTopicUrl(Project project) { + if (project == null || project.getName() == null) { + throw new IllegalArgumentException("Project and project name must not be null"); + } return String.join("/", getTopicsUrl(project), TOPIC_NAME); }server/src/test/java/com/flipkart/varadhi/web/admin/SubscriptionTestBase.java (2)
Line range hint
98-147
: Consider adding builder pattern for test data creation.While the implementation is clean, consider introducing a builder pattern for subscription creation to make tests more maintainable and readable. This would allow for easier customization of test data without needing multiple method overloads.
Example implementation:
public static class TestSubscriptionBuilder { private String name = "test-subscription"; private Project project = PROJECT; private VaradhiTopic topic; private boolean grouped = false; private RetryPolicy retryPolicy = DEFAULT_RETRY_POLICY; // ... other fields public TestSubscriptionBuilder withName(String name) { this.name = name; return this; } // ... other builder methods public VaradhiSubscription build() { return VaradhiSubscription.of( SubscriptionResource.buildInternalName(project.getName(), name), project.getName(), topic.getName(), UUID.randomUUID().toString(), grouped, DEFAULT_ENDPOINT, retryPolicy, DEFAULT_CONSUMPTION_POLICY, DEFAULT_SHARDS, DEFAULT_SUBSCRIPTION_PROPERTIES ); } }
150-174
: Consider using seeded Random for deterministic tests.Using a non-seeded Random instance could lead to non-deterministic test behavior. Consider using a seeded Random instance to ensure test reproducibility.
- private final Random random = new Random(); + private final Random random = new Random(42L); // Use a fixed seed for reproducibilityThis ensures that the random values generated in
createDlqMessage
andgenerateRandomBytes
are consistent across test runs, making it easier to debug test failures.server/src/test/java/com/flipkart/varadhi/web/WebTestBase.java (4)
51-58
: Good error handling in JSON deserialization!The error handling is well-implemented with proper exception wrapping. Consider adding logging before throwing the RuntimeException for better debugging capabilities.
} catch (Exception e) { + log.error("JSON deserialization failed", e); throw new RuntimeException("Failed to deserialize JSON", e); }
101-111
: Good error handling for unsupported HTTP methods!Consider using a switch statement or Map-based approach for better maintainability when handling HTTP methods.
public HttpRequest<Buffer> createRequest(HttpMethod method, String path) { - if (POST == method) { - return webClient.post(DEFAULT_PORT, DEFAULT_HOST, path); - } else if (GET == method) { - return webClient.get(DEFAULT_PORT, DEFAULT_HOST, path); - } else if (DELETE == method) { - return webClient.delete(DEFAULT_PORT, DEFAULT_HOST, path); - } else if (PUT == method) { - return webClient.put(DEFAULT_PORT, DEFAULT_HOST, path); - } else { - throw new UnsupportedOperationException("Unsupported HTTP method"); - } + return switch (method) { + case POST -> webClient.post(DEFAULT_PORT, DEFAULT_HOST, path); + case GET -> webClient.get(DEFAULT_PORT, DEFAULT_HOST, path); + case DELETE -> webClient.delete(DEFAULT_PORT, DEFAULT_HOST, path); + case PUT -> webClient.put(DEFAULT_PORT, DEFAULT_HOST, path); + default -> throw new UnsupportedOperationException("Unsupported HTTP method: " + method); + }; }
168-179
: Good error response handling!Consider adding validation for the error response structure to ensure all required fields are present.
ErrorResponse error = JsonMapper.jsonDeserialize(response.body().getBytes(), ErrorResponse.class); +assertNotNull(error, "Error response should not be null"); +assertNotNull(error.reason(), "Error reason should not be null"); assertEquals(expectedStatusMessage, error.reason());
220-223
: Good addition of PostResponseCapture for response handling!Consider making the fields volatile since they're accessed from different threads (main thread and callback thread).
private static class PostResponseCapture<T> { - private T response; - private Throwable throwable; + private volatile T response; + private volatile Throwable throwable; }server/src/test/java/com/flipkart/varadhi/web/admin/DlqHandlersTest.java (1)
Line range hint
298-313
: Consider simplifying URL building logic using URLBuilder or UriComponentsBuilder.The current string concatenation approach for building URLs with query parameters could be simplified using a dedicated URL building utility. This would:
- Reduce manual string manipulation
- Handle URL encoding automatically
- Make the code more maintainable
Example using UriComponentsBuilder:
private String getMessagesUrl(String subscriptionName, Project project, long failedAt, String nextPage, int limit) { UriComponentsBuilder builder = UriComponentsBuilder .fromPath(buildSubscriptionUrl(subscriptionName, project)) .path("/dlq/messages"); if (failedAt != 0) { builder.queryParam("earliestFailedAt", failedAt); } if (limit != -1) { builder.queryParam("limit", limit); } if (nextPage != null && !nextPage.isBlank()) { builder.queryParam("nextPage", nextPage); } return builder.build().toString(); }entities/src/test/java/com/flipkart/varadhi/entities/VaradhiSubscriptionTest.java (4)
16-36
: Document magic numbers in default test constants.Consider adding comments to explain the significance of magic numbers in the default constants (e.g., 500, 1, 10). This will help other developers understand the test scenarios better.
38-56
: Add assertion for initial subscription state.The successful creation test should also verify that the subscription's initial state is set correctly. Consider adding:
assertNotNull(subscription.getProperties()); +assertEquals(VaradhiSubscription.State.CREATING, subscription.getStatus().getState()); +assertNull(subscription.getStatus().getMessage());
103-116
: Add test for empty properties map.Consider adding a test case for an empty properties map to ensure it's handled correctly:
@Test void createSubscription_EmptyProperties_ThrowsException() { IllegalArgumentException exception = assertThrows( IllegalArgumentException.class, () -> { VaradhiSubscription.of( "sub1", "project1", "topic1", "description", true, DEFAULT_ENDPOINT, DEFAULT_RETRY_POLICY, DEFAULT_CONSUMPTION_POLICY, DEFAULT_SHARDS, Map.of() ); } ); assertEquals("Properties cannot be null or empty", exception.getMessage()); }
197-240
: Add edge cases for integer property retrieval.Consider adding tests for:
- Integer overflow/underflow
- Empty string value
- Whitespace-only string value
Example:
@Test void getIntProperty_IntegerOverflow_ThrowsException() { VaradhiSubscription subscription = VaradhiSubscription.of( "sub1", "project1", "topic1", "description", true, DEFAULT_ENDPOINT, DEFAULT_RETRY_POLICY, DEFAULT_CONSUMPTION_POLICY, DEFAULT_SHARDS, Map.of("key", "999999999999999999999999") ); assertThrows(NumberFormatException.class, () -> subscription.getIntProperty("key")); }server/src/test/java/com/flipkart/varadhi/services/SubscriptionServiceTest.java (2)
88-154
: Consider refactoring the setup method for better organization.The setup method mixes infrastructure initialization with test data preparation. Consider splitting it into separate methods:
setupInfrastructure()
for ZK, MetaStore, and service initializationsetupTestData()
for creating test entities- Consider extracting test data values into constants
@BeforeEach void setUp() throws Exception { + setupInfrastructure(); + setupTestData(); + setupMocks(); } + +private void setupInfrastructure() throws Exception { JsonMapper.getMapper().registerSubtypes( new NamedType(PulsarStorageTopic.class, "PulsarTopic"), new NamedType(PulsarSubscription.class, "PulsarSubscription") ); zkCuratorTestingServer = new TestingServer(); zkCurator = spy(CuratorFrameworkFactory.newClient( zkCuratorTestingServer.getConnectString(), new ExponentialBackoffRetry(1000, 1) )); zkCurator.start(); varadhiMetaStore = spy(new VaradhiMetaStore(zkCurator)); orgService = new OrgService(varadhiMetaStore); teamService = new TeamService(varadhiMetaStore); meterRegistry = new JmxMeterRegistry(JmxConfig.DEFAULT, Clock.SYSTEM); projectService = new ProjectService(varadhiMetaStore, "", meterRegistry); } + +private static final String TEST_ORG_NAME = "Org"; +private static final String TEST_TEAM_NAME = "Team"; +private static final String TEST_PROJECT1_NAME = "Project1"; +private static final String TEST_PROJECT2_NAME = "Project2"; + +private void setupTestData() { org = Org.of(TEST_ORG_NAME); team = Team.of(TEST_TEAM_NAME, org.getName()); project1 = Project.of(TEST_PROJECT1_NAME, "", team.getName(), team.getOrg()); project2 = Project.of(TEST_PROJECT2_NAME, "", team.getName(), team.getOrg()); unGroupedTopic = VaradhiTopic.of("UngroupedTopic", project1.getName(), false, null); groupedTopic = VaradhiTopic.of("GroupedTopic", project2.getName(), true, null); subscription1 = createUngroupedSubscription("Sub1", project1, unGroupedTopic); subscription2 = createUngroupedSubscription("Sub2", project1, unGroupedTopic); orgService.createOrg(org); teamService.createTeam(team); projectService.createProject(project1); projectService.createProject(project2); } + +private void setupMocks() { shardProvisioner = mock(ShardProvisioner.class); doNothing().when(shardProvisioner).provision(any(), any()); controllerRestApi = mock(ControllerRestApi.class); subscriptionService = new SubscriptionService(shardProvisioner, controllerRestApi, varadhiMetaStore); }
545-631
: Add test coverage for additional deletion scenarios.The deletion tests are thorough but consider adding the following test cases:
- Attempting to delete an already soft-deleted subscription
- Attempting to delete a subscription with active consumers
- Attempting to hard delete after a soft delete
Example test case:
@Test void deleteSubscription_AlreadySoftDeleted_ThrowsException() { doReturn(unGroupedTopic).when(varadhiMetaStore).getTopic(unGroupedTopic.getName()); subscriptionService.createSubscription(unGroupedTopic, subscription1, project1); // First soft delete CompletableFuture<SubscriptionState> stoppedState = CompletableFuture.completedFuture(SubscriptionState.forStopped()); doReturn(stoppedState).when(controllerRestApi) .getSubscriptionState(subscription1.getName(), REQUESTED_BY); subscriptionService.deleteSubscription( subscription1.getName(), project1, REQUESTED_BY, ResourceDeletionType.SOFT_DELETE).join(); // Attempt second delete InvalidOperationForResourceException exception = assertThrows( InvalidOperationForResourceException.class, () -> subscriptionService.deleteSubscription( subscription1.getName(), project1, REQUESTED_BY, ResourceDeletionType.SOFT_DELETE).join() ); assertEquals( "Cannot delete subscription in INACTIVE state", exception.getMessage() ); }server/src/test/java/com/flipkart/varadhi/web/admin/SubscriptionHandlersTest.java (5)
63-96
: Consider refactoring route configuration for better maintainability.The
configureRoutes
method could be improved by:
- Extracting route paths into constants to avoid magic strings
- Grouping related routes (e.g., lifecycle operations) into separate methods
Example refactor:
+ private static final String BASE_PATH = "/projects/:project/subscriptions"; + private static final String SUBSCRIPTION_PATH = BASE_PATH + "/:subscription"; + private void configureRoutes() { + configureBasicOperations(); + configureLifecycleOperations(); + } + + private void configureBasicOperations() { createRoute( - HttpMethod.POST, "/projects/:project/subscriptions", + HttpMethod.POST, BASE_PATH, subscriptionHandlers::create, true ); createRoute( - HttpMethod.GET, "/projects/:project/subscriptions/:subscription", + HttpMethod.GET, SUBSCRIPTION_PATH, subscriptionHandlers::get, false ); // ... other basic operations + } + + private void configureLifecycleOperations() { createRoute( - HttpMethod.POST, "/projects/:project/subscriptions/:subscription/restore", + HttpMethod.POST, SUBSCRIPTION_PATH + "/restore", subscriptionHandlers::restore, false ); // ... other lifecycle operations }
141-143
: Enhance error response validation in tests.Consider extending the error response validation to include:
- HTTP response headers verification
- Error message format consistency checks
- Error code validation where applicable
Example enhancement for one of the error cases:
ErrorResponse response = sendRequestWithEntity(request, resource, 401, errorMessage, ErrorResponse.class); + // Verify error response format assertEquals(errorMessage, response.reason()); + assertEquals("UNAUTHORIZED", response.code()); + verify(response.headers().contains("X-Error-Code"));Also applies to: 155-157, 169-171, 184-186, 197-199, 209-211, 221-223
258-306
: Add tests for concurrent deletion scenarios and resource cleanup.The deletion tests should be extended to cover:
- Concurrent deletion attempts
- Verification of related resource cleanup
- Rollback scenarios when deletion partially fails
Example additional test:
@Test void deleteSubscription_ConcurrentDeletion_HandlesGracefully() throws InterruptedException { HttpRequest<Buffer> request1 = createRequest( HttpMethod.DELETE, buildSubscriptionUrl("sub1", PROJECT) + "?deletionType=SOFT_DELETE" ); HttpRequest<Buffer> request2 = createRequest( HttpMethod.DELETE, buildSubscriptionUrl("sub1", PROJECT) + "?deletionType=SOFT_DELETE" ); doReturn(PROJECT).when(projectService).getCachedProject(PROJECT.getName()); doReturn(CompletableFuture.completedFuture(null)) .doThrow(new ConcurrentModificationException("Resource already being deleted")) .when(subscriptionService) .deleteSubscription(anyString(), eq(PROJECT), any(), eq(ResourceDeletionType.SOFT_DELETE)); sendRequestWithoutPayload(request1, null); ErrorResponse response = sendRequestWithoutPayload(request2, 409, "Resource already being deleted", ErrorResponse.class); assertEquals("Resource already being deleted", response.reason()); }
328-350
: Add tests for version conflicts and partial updates.The update tests should be extended to cover:
- Version conflict scenarios
- Partial update capabilities
- Update retries with version checks
Example additional test:
@Test void updateSubscription_VersionConflict_ThrowsConflictException() throws InterruptedException { HttpRequest<Buffer> request = createRequest(HttpMethod.PUT, buildSubscriptionUrl("sub1", PROJECT)); SubscriptionResource resource = createSubscriptionResource("sub1", PROJECT, TOPIC_RESOURCE); when(subscriptionService.updateSubscription( any(), anyInt(), anyString(), anyBoolean(), any(), any(), any(), any() )).thenThrow(new ConflictException("Version conflict")); ErrorResponse response = sendRequestWithEntity( request, resource, 409, "Version conflict", ErrorResponse.class); assertEquals("Version conflict", response.reason()); }
Line range hint
43-472
: Consider overall test improvements for better maintainability.While the test coverage is comprehensive, consider the following improvements:
- Add JavaDoc comments describing test scenarios and edge cases
- Implement test data builders to reduce duplication and improve maintainability
- Group related tests using nested test classes (JUnit 5 @nested)
Example implementation:
@Builder class SubscriptionTestData { private String name; private Project project; private TopicResource topic; private RetryPolicy retryPolicy; public static SubscriptionTestData defaultData() { return builder() .name("sub1") .project(Project.of("project1", "", "team1", "org1")) .topic(TopicResource.builder().build()) .retryPolicy(RetryPolicy.builder().build()) .build(); } } @Nested class SubscriptionCreationTests { @Test void whenValidInput_thenCreatesSuccessfully() { // Test implementation using SubscriptionTestData } }server/src/test/java/com/flipkart/varadhi/web/produce/ProduceHandlersTest.java (1)
125-125
: Consider extending test coverage for soft-deleted state.While the method replacement is correct, given the PR's objective to add soft-deletion support, consider adding test cases for the soft-deleted state alongside the existing Blocked, Throttled, and Replicating states.
server/src/test/java/com/flipkart/varadhi/services/DlqServiceTest.java (2)
182-183
: Enhance helper method to support deletion statesThe
setupSubscriptionForGetMessages
helper method should be enhanced to:
- Support creation of soft-deleted subscriptions
- Allow specification of subscription state for better test coverage
-private VaradhiSubscription setupSubscriptionForGetMessages() { +private VaradhiSubscription setupSubscriptionForGetMessages(boolean isDeleted) { String consumerId = "consumerId"; VaradhiTopic vTopic = TOPIC_RESOURCE.toVaradhiTopic(); VaradhiSubscription subscription = spy(createUngroupedSubscription("sub12", PROJECT, vTopic)); + when(subscription.isDeleted()).thenReturn(isDeleted); // ... rest of the method return subscription; }
Line range hint
1-196
: Enhance test coverage for soft-deletion functionalityWhile the code changes maintain the existing test functionality, the test suite needs to be expanded to thoroughly cover the new soft-deletion feature. Consider adding the following test cases:
- Lifecycle tests for soft-deleted subscriptions
- DLQ operations on restored subscriptions
- Error handling for various deletion states
This will ensure the robustness of the new deletion functionality and prevent potential issues in production.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (16)
entities/src/test/java/com/flipkart/varadhi/entities/VaradhiSubscriptionTest.java
(1 hunks)server/src/main/java/com/flipkart/varadhi/web/v1/admin/SubscriptionHandlers.java
(3 hunks)server/src/test/java/com/flipkart/varadhi/services/DlqServiceTest.java
(5 hunks)server/src/test/java/com/flipkart/varadhi/services/SubscriptionServiceTest.java
(2 hunks)server/src/test/java/com/flipkart/varadhi/web/WebTestBase.java
(5 hunks)server/src/test/java/com/flipkart/varadhi/web/admin/DlqHandlersTest.java
(8 hunks)server/src/test/java/com/flipkart/varadhi/web/admin/OrgHandlersTest.java
(4 hunks)server/src/test/java/com/flipkart/varadhi/web/admin/ProjectHandlersTest.java
(5 hunks)server/src/test/java/com/flipkart/varadhi/web/admin/SubscriptionHandlersTest.java
(2 hunks)server/src/test/java/com/flipkart/varadhi/web/admin/SubscriptionTestBase.java
(3 hunks)server/src/test/java/com/flipkart/varadhi/web/admin/TeamHandlersTest.java
(3 hunks)server/src/test/java/com/flipkart/varadhi/web/admin/TopicHandlersTest.java
(2 hunks)server/src/test/java/com/flipkart/varadhi/web/authz/IamPolicyHandlersTest.java
(2 hunks)server/src/test/java/com/flipkart/varadhi/web/produce/BodyHandlerTest.java
(1 hunks)server/src/test/java/com/flipkart/varadhi/web/produce/HeaderValidationTest.java
(5 hunks)server/src/test/java/com/flipkart/varadhi/web/produce/ProduceHandlersTest.java
(9 hunks)
✅ Files skipped from review due to trivial changes (1)
- server/src/test/java/com/flipkart/varadhi/web/authz/IamPolicyHandlersTest.java
⏰ Context from checks skipped due to timeout of 90000ms (1)
- GitHub Check: build
🔇 Additional comments (31)
server/src/test/java/com/flipkart/varadhi/web/produce/BodyHandlerTest.java (3)
52-53
: LGTM! Good test coverage for normal payload size.Testing with a 10-byte payload (well within the 20-byte limit) verifies basic successful case.
56-57
: LGTM! Good edge case testing.Testing with a 19-byte payload verifies behavior just under the limit.
60-61
: LGTM! Excellent boundary testing.Testing with exactly 20 bytes (at the limit) verifies the boundary condition.
server/src/test/java/com/flipkart/varadhi/web/produce/HeaderValidationTest.java (4)
65-65
: LGTM! Method name updated consistently.The change from
sendRequestWithByteBufferBody
tosendRequestWithPayload
maintains the test's functionality while using the new method name.Also applies to: 69-69
Line range hint
78-82
: LGTM! Error validation maintained.The method name update preserves the test's ability to verify header size constraints and error responses.
Line range hint
89-93
: LGTM! Value size validation preserved.The method name update maintains the test's validation of header value size constraints.
Line range hint
102-106
: LGTM! Header count validation maintained.The method name update preserves the test's ability to verify the maximum header count constraint.
server/src/main/java/com/flipkart/varadhi/web/v1/admin/SubscriptionHandlers.java (4)
36-39
: Well-structured class-level changes!Good improvements in code organization:
- Comprehensive class-level Javadoc
- Making
NUMBER_OF_RETRIES_ALLOWED
static final improves immutabilityAlso applies to: 44-44
230-231
: Consider addressing the TODO comments before merging.The comments indicate potential improvements:
- Splitting into separate update APIs
- Adding validation for unchanged updates
Would you like me to help create separate issues to track these improvements?
116-120
: LGTM: Well-implemented restoration endpoint!The restoration endpoint is properly:
- Authorized with SUBSCRIPTION_UPDATE permission
- Implemented as a non-blocking operation
- Returns the restored subscription resource
Also applies to: 271-273
333-336
: LGTM: Proper security check for ignoreConstraints.Good implementation of the super admin check for ignoring constraints. This ensures that only authorized users can bypass validation rules.
server/src/test/java/com/flipkart/varadhi/web/admin/SubscriptionTestBase.java (5)
4-15
: LGTM! Well-organized imports.The new imports are properly organized and necessary for the enhanced functionality.
40-82
: Well-structured constants and field declarations!Good improvements:
- Clear naming with DEFAULT_ prefix
- Descriptive variable names (e.g., 'random' instead of 'r')
- Proper organization of default configurations
- Clean mock declarations using annotations
84-88
: LGTM! Proper mock initialization.The setup follows testing best practices with proper parent class initialization and mock setup.
90-96
: Good method renaming from 'get' to 'build'!The rename better reflects that these methods construct URLs rather than retrieve resources.
177-183
: LGTM! Well-structured retry policy creation.The custom retry policy creation method is clean and follows the pattern of other factory methods in the class.
server/src/test/java/com/flipkart/varadhi/web/WebTestBase.java (3)
47-49
: Well-structured constant definitions!Good practice to extract magic numbers into named constants, improving code maintainability and readability.
84-87
: Good addition of assertion message!The descriptive assertion message will help in debugging test failures.
114-159
: Excellent improvements to request handling methods!
- Clear method naming distinguishing between payload and entity
- Good use of Optional for null handling
- Helpful assertion messages for debugging
server/src/test/java/com/flipkart/varadhi/web/admin/OrgHandlersTest.java (1)
67-67
: LGTM! Method renaming improves clarity.The renaming of request handling methods enhances readability and better aligns with REST terminology:
sendRequestWithBody
→sendRequestWithEntity
sendRequestWithoutBody
→sendRequestWithoutPayload
Also applies to: 73-73, 78-78, 98-98, 109-109, 115-115, 141-141, 146-146, 150-150
server/src/test/java/com/flipkart/varadhi/web/admin/ProjectHandlersTest.java (1)
83-83
: LGTM! Consistent method renaming across test files.The changes maintain consistency with the renaming pattern established in
OrgHandlersTest.java
.Also applies to: 89-89, 95-95, 100-100, 112-112, 118-118, 127-127, 132-132, 141-141, 151-151, 157-157, 163-163
server/src/test/java/com/flipkart/varadhi/web/admin/TeamHandlersTest.java (1)
85-85
: LGTM! Method renaming maintains consistency.The changes follow the same renaming pattern seen in other test files, improving overall codebase consistency.
Also applies to: 91-91, 97-97, 102-102, 114-114, 121-121
server/src/test/java/com/flipkart/varadhi/web/admin/DlqHandlersTest.java (1)
70-73
: LGTM! Method renaming improves clarity of intent.The renaming changes better reflect the methods' purpose:
getSubscriptionResource
→createSubscriptionResource
getUngroupedSubscription
→createUngroupedSubscription
entities/src/test/java/com/flipkart/varadhi/entities/VaradhiSubscriptionTest.java (1)
1-241
: Well-structured and comprehensive test suite.The test suite is well-organized and provides good coverage of the
VaradhiSubscription
class's functionality, particularly the state management features needed for soft deletion. The tests are focused, readable, and follow good testing practices.server/src/test/java/com/flipkart/varadhi/web/produce/ProduceHandlersTest.java (6)
102-102
: LGTM! Method replacement maintains error handling test.The change preserves the test's ability to verify ResourceNotFoundException handling.
143-147
: LGTM! Well-formatted error handling test.The method replacement maintains proper error handling verification with clear formatting.
157-161
: LGTM! Comprehensive error handling test coverage.The changes maintain proper verification of both expected (ResourceNotFoundException) and unexpected (RuntimeException) error scenarios.
171-171
: LGTM! ProduceException handling test maintained.The change preserves proper verification of ProduceException scenarios.
Line range hint
189-208
: LGTM! Header ordering and project validation tests maintained.The changes preserve proper verification of header ordering and non-existing project scenarios.
Line range hint
77-89
: LGTM! Verify the availability of sendRequestWithPayload method.The method renaming improves clarity while maintaining the test's functionality for message production and duplication scenarios.
✅ Verification successful
Verified: sendRequestWithPayload method is properly implemented and available
The method is correctly implemented in WebTestBase.java and properly handles payload conversion, status verification, and response deserialization. The usage in ProduceHandlersTest.java is consistent with its implementation.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Verify the existence and implementation of sendRequestWithPayload ast-grep --pattern 'sendRequestWithPayload($_, $_, $_)'Length of output: 1754
Script:
#!/bin/bash # Check the implementation of sendRequestWithPayload in WebTestBase ast-grep --pattern 'sendRequestWithPayload($_, $_, $_) { $$$ }'Length of output: 67
Script:
#!/bin/bash # Search for sendRequestWithPayload definition with context rg -U "sendRequestWithPayload.*?\{" -A 10 server/src/test/java/com/flipkart/varadhi/web/WebTestBase.javaLength of output: 106
Script:
#!/bin/bash # View the entire WebTestBase.java file cat server/src/test/java/com/flipkart/varadhi/web/WebTestBase.javaLength of output: 9205
server/src/test/java/com/flipkart/varadhi/services/DlqServiceTest.java (1)
83-83
: Add test coverage for soft-deleted subscriptionsThe test should verify DLQ message retrieval behavior when:
- The subscription is soft-deleted
- The subscription is restored from soft-deletion
@Test | ||
void restoreTopic_WithValidRequest_ShouldRestoreTopicSuccessfully() throws InterruptedException { | ||
HttpRequest<Buffer> request = createRequest(HttpMethod.POST, getTopicUrl(project) + "/restore"); | ||
doNothing().when(varadhiTopicService).restore(any()); | ||
|
||
sendRequestWithoutPayload(request, null); | ||
|
||
sendRequestWithoutBody(request, null); | ||
verify(varadhiTopicService, times(1)).delete(any()); | ||
verify(varadhiTopicService).restore(any()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add negative test cases for restore operation.
The restore operation only tests the happy path. Consider adding test cases for:
- Restoring a non-existent topic
- Restoring a topic that wasn't soft-deleted
- Restoring a hard-deleted topic
Example test case:
@Test
void restoreTopic_WithNonExistentTopic_ShouldReturnNotFound() throws InterruptedException {
HttpRequest<Buffer> request = createRequest(HttpMethod.POST, getTopicUrl(project) + "/restore");
doThrow(new TopicNotFoundException("Topic not found"))
.when(varadhiTopicService)
.restore(any());
HttpResponse<Buffer> response = sendRequestWithoutPayload(request, Buffer.class);
assertEquals(404, response.statusCode());
assertErrorResponse(response, "Topic not found");
}
@Test | ||
void restore_ChangesStateToCreated() { | ||
VaradhiSubscription subscription = VaradhiSubscription.of( | ||
"sub1", "project1", "topic1", "description", true, | ||
DEFAULT_ENDPOINT, DEFAULT_RETRY_POLICY, DEFAULT_CONSUMPTION_POLICY, | ||
DEFAULT_SHARDS, Map.of("key", "value") | ||
); | ||
|
||
subscription.markInactive(); | ||
subscription.restore(); | ||
assertEquals(VaradhiSubscription.State.CREATED, subscription.getStatus().getState()); | ||
assertEquals("Entity restored to created state.", subscription.getStatus().getMessage()); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Enhance restore state transition tests.
The restore test should:
- Verify that restoration is only possible from INACTIVE state
- Test restoration attempts from other states
Add these test cases:
@Test
void restore_FromNonInactiveState_ThrowsException() {
VaradhiSubscription subscription = VaradhiSubscription.of(
"sub1", "project1", "topic1", "description", true,
DEFAULT_ENDPOINT, DEFAULT_RETRY_POLICY, DEFAULT_CONSUMPTION_POLICY,
DEFAULT_SHARDS, Map.of("key", "value")
);
subscription.markDeleting();
IllegalStateException exception = assertThrows(
IllegalStateException.class,
subscription::restore
);
assertEquals("Can only restore from INACTIVE state", exception.getMessage());
}
private void assertSubscriptionsEqual(VaradhiSubscription expected, VaradhiSubscription actual) { | ||
assertEquals(expected.getName(), actual.getName()); | ||
assertEquals(expected.getTopic(), actual.getTopic()); | ||
assertEquals(expected.isGrouped(), actual.isGrouped()); | ||
assertEquals(expected.getDescription(), actual.getDescription()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Enhance subscription comparison in assertSubscriptionsEqual.
The helper method doesn't compare all relevant fields of the subscription. Consider adding comparisons for:
- Endpoint configuration
- Retry policy
- Consumption policy
- Version
- Status
private void assertSubscriptionsEqual(VaradhiSubscription expected, VaradhiSubscription actual) {
assertEquals(expected.getName(), actual.getName());
assertEquals(expected.getTopic(), actual.getTopic());
assertEquals(expected.isGrouped(), actual.isGrouped());
assertEquals(expected.getDescription(), actual.getDescription());
+ assertEquals(expected.getEndpoint(), actual.getEndpoint());
+ assertEquals(expected.getRetryPolicy(), actual.getRetryPolicy());
+ assertEquals(expected.getConsumptionPolicy(), actual.getConsumptionPolicy());
+ assertEquals(expected.getVersion(), actual.getVersion());
+ assertEquals(expected.getStatus(), actual.getStatus());
}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
private void assertSubscriptionsEqual(VaradhiSubscription expected, VaradhiSubscription actual) { | |
assertEquals(expected.getName(), actual.getName()); | |
assertEquals(expected.getTopic(), actual.getTopic()); | |
assertEquals(expected.isGrouped(), actual.isGrouped()); | |
assertEquals(expected.getDescription(), actual.getDescription()); | |
private void assertSubscriptionsEqual(VaradhiSubscription expected, VaradhiSubscription actual) { | |
assertEquals(expected.getName(), actual.getName()); | |
assertEquals(expected.getTopic(), actual.getTopic()); | |
assertEquals(expected.isGrouped(), actual.isGrouped()); | |
assertEquals(expected.getDescription(), actual.getDescription()); | |
assertEquals(expected.getEndpoint(), actual.getEndpoint()); | |
assertEquals(expected.getRetryPolicy(), actual.getRetryPolicy()); | |
assertEquals(expected.getConsumptionPolicy(), actual.getConsumptionPolicy()); | |
assertEquals(expected.getVersion(), actual.getVersion()); | |
assertEquals(expected.getStatus(), actual.getStatus()); | |
} |
@Test | ||
void restoreSubscription_ValidRequest_ReturnsRestoredSubscription() throws InterruptedException { | ||
HttpRequest<Buffer> request = createRequest( | ||
HttpMethod.POST, | ||
buildSubscriptionUrl("sub1", PROJECT) + "/restore" | ||
); | ||
SubscriptionResource resource = createSubscriptionResource("sub1", PROJECT, TOPIC_RESOURCE); | ||
VaradhiSubscription subscription = | ||
createUngroupedSubscription("sub1", PROJECT, TOPIC_RESOURCE.toVaradhiTopic()); | ||
|
||
doReturn(CompletableFuture.completedFuture(subscription)).when(subscriptionService) | ||
.restoreSubscription(any(), any()); | ||
|
||
SubscriptionResource restored = sendRequestWithoutPayload(request, SubscriptionResource.class); | ||
|
||
assertEquals(resource.getName(), restored.getName()); | ||
verify(subscriptionService, times(1)).restoreSubscription(any(), any()); | ||
} | ||
|
||
@Test | ||
void startSubscription_ValidRequest_TriggersStart() throws InterruptedException { | ||
HttpRequest<Buffer> request = createRequest( | ||
HttpMethod.POST, | ||
buildSubscriptionUrl("sub1", PROJECT) + "/start" | ||
); | ||
|
||
doReturn(CompletableFuture.completedFuture(null)).when(subscriptionService).start(any(), any()); | ||
|
||
sendRequestWithoutPayload(request, null); | ||
|
||
verify(subscriptionService, times(1)).start(any(), any()); | ||
} | ||
|
||
@Test | ||
void stopSubscription_ValidRequest_TriggersStop() throws InterruptedException { | ||
HttpRequest<Buffer> request = createRequest( | ||
HttpMethod.POST, | ||
buildSubscriptionUrl("sub1", PROJECT) + "/stop" | ||
); | ||
|
||
doReturn(CompletableFuture.completedFuture(null)).when(subscriptionService).stop(any(), any()); | ||
|
||
sendRequestWithoutPayload(request, null); | ||
|
||
verify(subscriptionService, times(1)).stop(any(), any()); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Enhance lifecycle operation test coverage.
The lifecycle operation tests should be extended to cover:
- Error scenarios (e.g., restoring an active subscription)
- Invalid state transitions
- Concurrent operation handling
Example additional tests:
@Test
void restoreSubscription_AlreadyActive_ThrowsIllegalStateException() throws InterruptedException {
HttpRequest<Buffer> request = createRequest(
HttpMethod.POST,
buildSubscriptionUrl("sub1", PROJECT) + "/restore"
);
doReturn(CompletableFuture.failedFuture(
new IllegalStateException("Subscription is already active")
)).when(subscriptionService).restoreSubscription(any(), any());
ErrorResponse response = sendRequestWithoutPayload(
request, 400, "Subscription is already active", ErrorResponse.class);
assertEquals("Subscription is already active", response.reason());
}
@Test
void startSubscription_AlreadyStopped_ThrowsIllegalStateException() throws InterruptedException {
HttpRequest<Buffer> request = createRequest(
HttpMethod.POST,
buildSubscriptionUrl("sub1", PROJECT) + "/start"
);
doReturn(CompletableFuture.failedFuture(
new IllegalStateException("Subscription is stopped")
)).when(subscriptionService).start(any(), any());
ErrorResponse response = sendRequestWithoutPayload(
request, 400, "Subscription is stopped", ErrorResponse.class);
assertEquals("Subscription is stopped", response.reason());
}
VaradhiTopic vTopic = TOPIC_RESOURCE.toVaradhiTopic(); | ||
VaradhiSubscription subscription = spy(createUngroupedSubscription("sub12", PROJECT, vTopic)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Enhance error handling for deletion states
The test verifies invalid state handling but should be extended to:
- Include specific error messages for deletion states
- Distinguish between different types of invalid states (not provisioned vs. deleted)
@Test
void testUnsidelineInvalidState() {
VaradhiTopic vTopic = TOPIC_RESOURCE.toVaradhiTopic();
VaradhiSubscription subscription = spy(createUngroupedSubscription("sub12", PROJECT, vTopic));
when(subscription.isWellProvisioned()).thenReturn(false);
+ // Add test for deleted state
+ when(subscription.isDeleted()).thenReturn(true);
InvalidOperationForResourceException exception = assertThrows(
InvalidOperationForResourceException.class,
() -> dlqService.unsideline(subscription, UnsidelineRequest.ofFailedAt(100), "testUser")
);
- assertTrue(exception.getMessage().contains("Unsideline not allowed"));
+ assertTrue(exception.getMessage().contains("Unsideline not allowed on deleted subscription"));
}
Committable suggestion skipped: line range outside the PR's diff.
VaradhiTopic vTopic = TOPIC_RESOURCE.toVaradhiTopic(); | ||
VaradhiSubscription subscription = spy(createUngroupedSubscription("sub12", PROJECT, vTopic)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Codebase verification
Add test cases for deletion state scenarios
The current test suite lacks coverage for the new deletion states. Add test cases to verify:
- DLQ operations (unsideline, getMessages) are properly rejected for soft-deleted subscriptions
- Error messages clearly indicate when operations are attempted on deleted resources
- Proper exception types are thrown for deleted state scenarios
Specific test cases needed:
testUnsidelineDeletedSubscription
testGetMessagesDeletedSubscription
🔗 Analysis chain
Add test cases for soft-deletion scenarios
Given the PR's focus on implementing soft-deletion support, this test method should include additional scenarios to verify DLQ behavior with soft-deleted resources:
- Attempting to unsideline messages for soft-deleted subscriptions
- Verifying proper error handling when subscription is in deleted state
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Check if similar test cases exist elsewhere
rg -g '*.java' -B 5 -A 5 'test.*Deleted.*Unsideline|test.*Unsideline.*Deleted'
Length of output: 80
Script:
#!/bin/bash
# Search for deletion-related tests in DlqService and subscription tests
rg -g '*.java' -B 5 -A 5 'class.*DlqServiceTest|class.*SubscriptionServiceTest'
# Search for test methods related to deletion states
ast-grep --pattern 'void test$_Deleted$_() {
$$$
}'
Length of output: 2567
Script:
#!/bin/bash
# Get all test methods from DlqServiceTest
rg -g 'DlqServiceTest.java' '^[[:space:]]*@Test'
# Check for state-related methods and constants
rg -g 'DlqServiceTest.java|SubscriptionTestBase.java' 'ResourceState|DELETED|ACTIVE'
# Look for subscription creation and state handling
ast-grep --pattern 'createUngroupedSubscription($$$) {
$$$
}'
Length of output: 998
Script:
#!/bin/bash
# Get the content of test methods in DlqServiceTest
rg -g 'DlqServiceTest.java' -B 1 -A 10 '@Test'
# Check for any state-related code in the test file
cat server/src/test/java/com/flipkart/varadhi/services/DlqServiceTest.java
Length of output: 22389
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (4)
server/src/main/java/com/flipkart/varadhi/web/v1/admin/SubscriptionHandlers.java (2)
251-254
: Consider using a constant for the default deletion type.The default deletion type could be extracted to a constant to improve maintainability and make the default behavior more explicit.
+ private static final ResourceDeletionType DEFAULT_DELETION_TYPE = ResourceDeletionType.SOFT_DELETE; ResourceDeletionType deletionType = ctx.queryParam(QUERY_PARAM_DELETION_TYPE).stream() .map(ResourceDeletionType::fromValue) .findFirst() - .orElse(ResourceDeletionType.SOFT_DELETE); + .orElse(DEFAULT_DELETION_TYPE);
413-418
: Enhance property validation error handling.Consider these improvements to the property validation:
- Provide more descriptive error messages including the invalid value
- Collect all validation errors before throwing the exception
+ List<String> validationErrors = new ArrayList<>(); propertyValidators.forEach((key, validator) -> { String value = properties.get(key); if (!validator.isValid(value, usePermissible)) { - throw new IllegalArgumentException("Invalid value for property: " + key); + validationErrors.add(String.format("Invalid value '%s' for property: %s", value, key)); } }); + if (!validationErrors.isEmpty()) { + throw new IllegalArgumentException("Validation failed:\n" + String.join("\n", validationErrors)); + }server/src/test/java/com/flipkart/varadhi/web/admin/SubscriptionHandlersTest.java (2)
63-107
: Consider extracting route configuration to the base class.The
configureRoutes
method and its helpercreateRoute
contain common route setup logic that could be reused by other handler tests.Consider moving this to
SubscriptionTestBase
:- private void configureRoutes() { + protected void configureRoutes() {- private void createRoute(HttpMethod method, String path, Handler<RoutingContext> handler, boolean requiresBody) { + protected void createRoute(HttpMethod method, String path, Handler<RoutingContext> handler, boolean requiresBody) {
338-344
: Consider using ArgumentMatchers for better readability.The test uses a mix of argument captors and matchers. Consider using specific argument matchers for better readability.
- when(subscriptionService.updateSubscription( - stringCaptor.capture(), integerCaptor.capture(), anyString(), anyBoolean(), any(), any(), any(), - any() - )).thenReturn( + when(subscriptionService.updateSubscription( + eq("project1.sub1"), eq(0), anyString(), anyBoolean(), any(), any(), any(), + any() + )).thenReturn(
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
server/src/main/java/com/flipkart/varadhi/web/v1/admin/SubscriptionHandlers.java
(3 hunks)server/src/test/java/com/flipkart/varadhi/web/admin/SubscriptionHandlersTest.java
(2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (2)
- GitHub Check: call-workflow-docker-build / build
- GitHub Check: build
🔇 Additional comments (7)
server/src/main/java/com/flipkart/varadhi/web/v1/admin/SubscriptionHandlers.java (3)
43-44
: LGTM! Good improvements to initialization and constants.The changes improve code quality through:
- Making NUMBER_OF_RETRIES_ALLOWED a proper constant with static final
- Cleaner initialization of validators
- Better documentation with comprehensive Javadoc
Also applies to: 73-75
116-120
: LGTM! Well-structured restore endpoint.The new restore endpoint is properly implemented with:
- Appropriate authorization (SUBSCRIPTION_UPDATE)
- RESTful convention (/restore)
- Non-blocking execution
271-274
: LGTM! Clean implementation of restore functionality.The restore method follows the established patterns:
- Proper error handling through handleResponse
- Consistent with other endpoint implementations
server/src/test/java/com/flipkart/varadhi/web/admin/SubscriptionHandlersTest.java (4)
47-51
: LGTM! Good use of argument captors.The use of
@Captor
for string and integer arguments will help in verifying the exact values passed to the service methods.
257-306
: Comprehensive test coverage for deletion types.Good test coverage for soft deletion scenarios including:
- Explicit soft delete
- Default to soft delete when no type is specified
- Proper verification of service method calls
427-468
: Enhance lifecycle operation tests.While the basic lifecycle operation tests are present, they could be enhanced to cover more scenarios.
Consider adding tests for:
- Error scenarios (e.g., restoring an active subscription)
- Invalid state transitions
- Concurrent operation handling
Example:
@Test void restoreSubscription_AlreadyActive_ThrowsIllegalStateException() throws InterruptedException { HttpRequest<Buffer> request = createRequest( HttpMethod.POST, buildSubscriptionUrl("sub1", PROJECT) + "/restore" ); doReturn(CompletableFuture.failedFuture( new IllegalStateException("Subscription is already active") )).when(subscriptionService).restoreSubscription(any(), any()); ErrorResponse response = sendRequestWithoutPayload( request, 400, "Subscription is already active", ErrorResponse.class); assertEquals("Subscription is already active", response.reason()); }
189-200
: Add test for maximum retry limit configuration.The test hardcodes the maximum retry limit to 3. Consider parameterizing this value based on configuration.
…lidation - Added message as an input query parameter in delete and restore requests (not mandatory). - Introduced Action Code as an enum to track who performed the action (User, Varadhi Admin, System). - Implemented logic to allow actions based on who performed the previous action. - Updated validation logic for topic deletion: - Soft deletion of a topic is allowed only if all subscriptions are soft deleted. - Hard deletion of a topic is allowed only if all subscriptions are hard deleted. - Added `includeInactive` flag to resource listing to include inactive resources. - Added unit tests to verify the new functionality. - Added a generic `LifecycleStatus` class to handle both topic and subscription lifecycle statuses. - Refactored existing code to use the new `LifecycleStatus` class.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 7
♻️ Duplicate comments (2)
server/src/main/java/com/flipkart/varadhi/services/VaradhiTopicService.java (1)
104-118
: 🛠️ Refactor suggestionImplement rollback mechanism for hard delete failures.
If storage topic deletion fails for some regions, the system could be left in an inconsistent state.
Consider implementing a two-phase deletion:
- Mark all storage topics for deletion
- Execute deletion with rollback capability
server/src/main/java/com/flipkart/varadhi/services/SubscriptionService.java (1)
338-355
:⚠️ Potential issueImplement retry mechanism for hard deletion.
The hard deletion implementation could leave the subscription in an inconsistent state if
deProvision
fails after updating the status but before completing deletion.Consider implementing a retry mechanism or transaction to ensure atomicity:
private void handleHardDelete( VaradhiSubscription subscription, Project subProject, ResourceActionRequest actionRequest ) { subscription.markDeleting(actionRequest.actionCode(), actionRequest.message()); metaStore.updateSubscription(subscription); + int maxRetries = 3; + int retryCount = 0; try { - shardProvisioner.deProvision(subscription, subProject); - metaStore.deleteSubscription(subscription.getName()); - log.info("Subscription '{}' deleted successfully.", subscription.getName()); + while (retryCount < maxRetries) { + try { + shardProvisioner.deProvision(subscription, subProject); + metaStore.deleteSubscription(subscription.getName()); + log.info("Subscription '{}' deleted successfully.", subscription.getName()); + return; + } catch (Exception e) { + retryCount++; + if (retryCount == maxRetries) throw e; + log.warn("Retry {} of {} for deleting subscription '{}'", + retryCount, maxRetries, subscription.getName(), e); + Thread.sleep(1000 * retryCount); + } + } } catch (Exception e) { log.error("Failed to hard delete subscription '{}'.", subscription.getName(), e); subscription.markDeleteFailed(e.getMessage()); metaStore.updateSubscription(subscription); throw e; } }
🧹 Nitpick comments (26)
server/src/main/java/com/flipkart/varadhi/web/routes/RouteDefinition.java (1)
71-74
: LGTM! Consider adding method-level documentation.The implementation correctly adds PATCH support following the existing pattern. This aligns well with the PR objective of implementing soft deletion functionality, as PATCH is the appropriate HTTP method for partial updates like marking resources as deleted.
Consider adding method-level documentation to explain the purpose and usage, similar to other HTTP method builders:
+ /** + * Creates a new Builder instance for PATCH requests. + * @param name The name of the route + * @param path The path pattern for the route + * @return A new Builder instance configured for PATCH method + */ public static Builder patch(String name, String path) { return new Builder(name, HttpMethod.PATCH, path); }server/src/test/java/com/flipkart/varadhi/web/entities/TopicResourceTest.java (1)
17-77
: Add test coverage for edge cases and error conditions.While the current tests cover the basic functionality well, consider adding tests for:
Edge cases:
- Topics with special characters in names
- Maximum length names
- Empty or null project names
Error conditions:
- Invalid action codes
- Null capacity policies
- Invalid conversions
Negative scenarios:
- Validation failures
- Conversion failures
Here's a sample test for validation failures:
@Test void from_ThrowsException_WhenInvalidTopicName() { assertThrows(IllegalArgumentException.class, () -> { VaradhiTopic varadhiTopic = VaradhiTopic.of( "projectName", "invalid/topic/name", true, new TopicCapacityPolicy(), LifecycleStatus.ActionCode.SYSTEM_ACTION ); TopicResource.from(varadhiTopic); }); }server/src/main/java/com/flipkart/varadhi/utils/VaradhiTopicFactory.java (1)
22-27
: Consider creating a tracking issue for the TODO.The comment indicates that the
deploymentRegion
field is a temporary solution. To ensure this technical debt is properly tracked and addressed:
- The region should be derived from TopicResource
- Regional/HA/BCP-DR policies need to be implemented
Would you like me to help create an issue to track this technical debt?
server/src/test/java/com/flipkart/varadhi/web/admin/TopicHandlersTest.java (2)
88-115
: Consider breaking down the route setup method for better maintainability.The
setupRoutes
method handles multiple responsibilities. Consider extracting each route setup into separate methods for better organization and readability.- private void setupRoutes() { + private void setupRoutes() { + setupCreateTopicRoute(); + setupGetTopicRoute(); + setupListTopicsRoute(); + setupDeleteTopicRoute(); + setupRestoreTopicRoute(); + setupFailureHandlers(); + } + + private void setupCreateTopicRoute() { router.post("/projects/:project/topics") .handler(bodyHandler) .handler(ctx -> { topicHandlers.setRequestBody(ctx); ctx.next(); }) .handler(ctx -> { requestTelemetryConfigurator.addRequestSpanAndLog(ctx, "CreateTopic", TelemetryType.ALL); ctx.next(); }) .handler(wrapBlocking(topicHandlers::create)); + }
229-247
: Consider adding edge cases for deletion type validation.While the deletion type tests are comprehensive, consider adding test cases for:
- Empty string deletion type
- Case-insensitive deletion type handling
@Test void deleteTopic_WithEmptyDeletionType_ShouldDefaultToDefault() throws InterruptedException { verifyDeleteRequest("", ResourceDeletionType.DEFAULT); } @Test void deleteTopic_WithCaseInsensitiveDeletionType_ShouldWork() throws InterruptedException { verifyDeleteRequest("soft_delete", ResourceDeletionType.SOFT_DELETE); }server/src/test/java/com/flipkart/varadhi/utils/VaradhiTopicFactoryTest.java (1)
90-112
: Consider alternatives to testing private methods with reflection.While the test is well-implemented, using reflection to test private methods is generally not recommended as it:
- Makes tests brittle and harder to maintain
- May indicate that the private method should be extracted into a separate class
Consider either:
- Moving the deployment logic to a separate strategy class
- Making the method package-private for testing
- Testing the behavior through public methods instead
server/src/test/java/com/flipkart/varadhi/web/WebTestBase.java (1)
48-55
: Enhance error message in JSON deserialization.While the error handling is good, consider including more context in the error message.
- throw new RuntimeException("Failed to deserialize JSON", e); + throw new RuntimeException( + String.format("Failed to deserialize JSON to %s<%s>", collectionClass.getSimpleName(), clazz.getSimpleName()), + e + );server/src/test/java/com/flipkart/varadhi/web/entities/SubscriptionResourceTest.java (2)
17-35
: Add test cases for edge cases and invalid inputs.While the happy path is well tested, consider adding test cases for:
- Invalid subscription names (null, empty, special characters)
- Null project or topic
- Edge cases for other properties
This will ensure the validation logic is working correctly.
61-73
: Add test cases for internal name edge cases.Consider adding test cases for:
- Project names with special characters
- Subscription names with special characters
- Empty or null project/subscription names
- Maximum length validation
This will ensure the internal name generation is robust against all inputs.
entities/src/main/java/com/flipkart/varadhi/entities/VaradhiTopic.java (2)
43-61
: Consider adding parameter validation.While the factory method changes are good, consider adding validation for the
actionCode
parameter to prevent null values.public static VaradhiTopic of( String project, String name, boolean grouped, TopicCapacityPolicy capacity, LifecycleStatus.ActionCode actionCode ) { + if (actionCode == null) { + throw new IllegalArgumentException("Action code cannot be null"); + } return new VaradhiTopic( buildTopicName(project, name), INITIAL_VERSION, grouped, capacity, new HashMap<>(), new LifecycleStatus(LifecycleStatus.State.ACTIVE, actionCode) ); }
107-135
: Consider adding parameter validation and thread safety.The lifecycle management methods look good but consider:
- Adding null checks for parameters in
markActive
andmarkInactive
.- Adding synchronization or making the methods thread-safe if concurrent modifications are possible.
public void markActive(LifecycleStatus.ActionCode actionCode, String message) { + if (actionCode == null) { + throw new IllegalArgumentException("Action code cannot be null"); + } + if (message == null) { + throw new IllegalArgumentException("Message cannot be null"); + } this.status.update(LifecycleStatus.State.ACTIVE, message, actionCode); } public void markInactive(LifecycleStatus.ActionCode actionCode, String message) { + if (actionCode == null) { + throw new IllegalArgumentException("Action code cannot be null"); + } + if (message == null) { + throw new IllegalArgumentException("Message cannot be null"); + } this.status.update(LifecycleStatus.State.INACTIVE, message, actionCode); }If concurrent modifications are possible, consider adding synchronization:
+import java.util.concurrent.locks.ReentrantReadWriteLock; public class VaradhiTopic extends AbstractTopic { + private final ReentrantReadWriteLock statusLock = new ReentrantReadWriteLock(); public void markActive(LifecycleStatus.ActionCode actionCode, String message) { + statusLock.writeLock().lock(); + try { this.status.update(LifecycleStatus.State.ACTIVE, message, actionCode); + } finally { + statusLock.writeLock().unlock(); + } } public void markInactive(LifecycleStatus.ActionCode actionCode, String message) { + statusLock.writeLock().lock(); + try { this.status.update(LifecycleStatus.State.INACTIVE, message, actionCode); + } finally { + statusLock.writeLock().unlock(); + } } @JsonIgnore public boolean isActive() { + statusLock.readLock().lock(); + try { return this.status.getState() == LifecycleStatus.State.ACTIVE; + } finally { + statusLock.readLock().unlock(); + } } }server/src/test/java/com/flipkart/varadhi/utils/VaradhiSubscriptionFactoryTest.java (2)
25-40
: Consider moving test constants to a shared test constants file.The test constants could be reused across other test classes. Consider extracting them to a shared test constants file to promote reusability and maintainability.
41-47
: Consider verifying mock initialization.While the setup is correct, consider adding assertions to verify that the mocks are properly initialized and injected. This can help catch initialization issues early.
Example:
@Override @BeforeEach public void setUp() { MockitoAnnotations.openMocks(this); varadhiSubscriptionFactory = new VaradhiSubscriptionFactory(topicService, subscriptionFactory, topicFactory, REGION); + assertNotNull(subscriptionFactory, "subscriptionFactory should be initialized"); + assertNotNull(topicFactory, "topicFactory should be initialized"); + assertNotNull(topicService, "topicService should be initialized"); }entities/src/test/java/com/flipkart/varadhi/entities/VaradhiTopicTest.java (2)
19-31
: Consider adding Javadoc to document the helper class and method.The helper class and method effectively reduce code duplication. Consider adding Javadoc to explain their purpose and parameters.
+/** + * A dummy implementation of StorageTopic for testing purposes. + */ @EqualsAndHashCode(callSuper = true) public static class DummyStorageTopic extends StorageTopic { +/** + * Creates a VaradhiTopic instance with default parameters. + * @param grouped whether the topic is grouped + * @return a new VaradhiTopic instance + */ private VaradhiTopic createDefaultVaradhiTopic(boolean grouped) {
111-146
: Consider adding tests for invalid state transitions.The tests cover basic state transitions well. Consider adding tests for:
- Invalid state transitions (e.g., attempting to activate an already active topic)
- Invalid action codes
- Empty or null reason messages
+@Test +void markActive_WhenAlreadyActive_ThrowsException() { + VaradhiTopic varadhiTopic = createDefaultVaradhiTopic(false); + + assertThrows(IllegalStateException.class, () -> + varadhiTopic.markActive(LifecycleStatus.ActionCode.SYSTEM_ACTION, "Already active")); +} +@Test +void markInactive_WithNullReason_ThrowsException() { + VaradhiTopic varadhiTopic = createDefaultVaradhiTopic(false); + + assertThrows(IllegalArgumentException.class, () -> + varadhiTopic.markInactive(LifecycleStatus.ActionCode.SYSTEM_ACTION, null)); +}server/src/main/java/com/flipkart/varadhi/services/VaradhiTopicService.java (3)
55-70
: Enhance error message for inactive topics.The error message could be more descriptive by indicating that the topic exists but is inactive.
- throw new ResourceNotFoundException("Topic %s not found.".formatted(topicName)); + throw new ResourceNotFoundException("Topic %s exists but is inactive.".formatted(topicName));
81-81
: Track TODO for namespace cleanup.The TODO comment about namespace cleanup should be tracked in the issue tracker.
Would you like me to create an issue to track the namespace cleanup implementation?
203-206
: Consider pushing filter to database layer.The current implementation loads all topics and filters in memory. Consider adding a filter parameter to the MetaStore query.
entities/src/main/java/com/flipkart/varadhi/entities/auth/ResourceAction.java (1)
91-118
: Consider tightening validation or naming for the nestedAction
enum
The addition of multiple actions (RESTORE, CONSUME, PRODUCE, etc.) is comprehensive. However, if certain actions require special handling or mandatory parameters at runtime, consider adding validations or distinct methods to prevent misuse. For example, some consumers might attempt toRESTORE
orMIGRATE
resources without the correct prerequisites.server/src/main/java/com/flipkart/varadhi/web/entities/TopicResource.java (1)
22-32
: Check field mutability versus final usage
Fieldscapacity
(line 26) andactionCode
(line 31) are assigned in the constructor but also have@Setter
. Confirm if it’s necessary for these fields to be mutable after construction; if not, consider removing the setters to maintain immutability and reduce accidental state changes.server/src/main/java/com/flipkart/varadhi/utils/VaradhiSubscriptionFactory.java (1)
157-196
: Improve shard capacity and multi-shard logic
ThegetMultiShard
andgetShardCapacity
methods (lines 157–196) cleanly separate single vs. multiple shard creation. However, if advanced load balancing or dynamic scaling is introduced in the future, you may want to expand these methods to factor in usage metrics or cluster constraints beyond a simple1/shardCount
capacity split.entities/src/main/java/com/flipkart/varadhi/entities/ResourceActionRequest.java (1)
14-18
: Consider enhancing the error message.The error message could be more descriptive to help with debugging.
- throw new NullPointerException("actionCode must not be null"); + throw new NullPointerException("ResourceActionRequest: actionCode must not be null");entities/src/test/java/com/flipkart/varadhi/entities/ResourceActionRequestTest.java (1)
10-59
: Consider adding tests for Validatable interface.Since
ResourceActionRequest
implementsValidatable
, consider adding tests to verify the validation behavior.@Test void validate_ValidatesSuccessfully() { ResourceActionRequest request = new ResourceActionRequest( LifecycleStatus.ActionCode.SYSTEM_ACTION, "Test message" ); request.validate(); // Should not throw any exception }entities/src/test/java/com/flipkart/varadhi/entities/LifecycleStatusTest.java (1)
10-68
: Consider adding tests for invalid state transitions.Since this is related to lifecycle management, it would be valuable to add tests that verify the behavior when invalid state transitions are attempted.
@Test void update_InvalidStateTransition_ThrowsException() { LifecycleStatus status = new LifecycleStatus( LifecycleStatus.State.DELETED, LifecycleStatus.ActionCode.SYSTEM_ACTION ); assertThrows(IllegalStateException.class, () -> status.update(LifecycleStatus.State.CREATING, "Invalid transition") ); }entities/src/testFixtures/java/com/flipkart/varadhi/entities/SubscriptionUtils.java (1)
56-59
: Consider adding validation for negative test scenarios.The subscription creation with
LifecycleStatus.ActionCode.SYSTEM_ACTION
looks good, but consider adding utility methods for creating subscriptions with different lifecycle states to support negative test cases.+ /** + * Creates a subscription with a specific lifecycle status for testing. + * + * @param name the subscription name + * @param project the project name + * @param description the description + * @param actionCode the lifecycle status action code + * @return the subscription + */ + public static VaradhiSubscription createWithStatus( + String name, + String project, + String description, + LifecycleStatus.ActionCode actionCode + ) { + return TopicResource.unGrouped(name, project, description, actionCode); + }server/src/main/java/com/flipkart/varadhi/web/v1/admin/TopicHandlers.java (1)
183-197
: Consider adding transaction boundaries for deletion operations.The deletion logic looks good, but there's a potential race condition between state check and actual deletion. Consider adding transaction boundaries or optimistic locking.
Also, enhance logging to include the deletion type for better traceability:
- log.info("Subscription '{}' deleted successfully.", subscription.getName()); + log.info("Subscription '{}' {} successfully.", subscription.getName(), + deletionType.equals(ResourceDeletionType.HARD_DELETE) ? "hard deleted" : "soft deleted");
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (47)
common/src/main/java/com/flipkart/varadhi/Constants.java
(3 hunks)controller/src/test/java/com/flipkart/varadhi/controller/AssignmentManagerTest.java
(6 hunks)controller/src/test/java/com/flipkart/varadhi/controller/ControllerApiMgrTest.java
(22 hunks)controller/src/test/java/com/flipkart/varadhi/controller/OperationMgrTest.java
(17 hunks)controller/src/test/java/com/flipkart/varadhi/controller/impl/LeastAssignedStrategyTests.java
(5 hunks)entities/src/main/java/com/flipkart/varadhi/entities/LifecycleStatus.java
(1 hunks)entities/src/main/java/com/flipkart/varadhi/entities/ResourceActionRequest.java
(1 hunks)entities/src/main/java/com/flipkart/varadhi/entities/VaradhiSubscription.java
(4 hunks)entities/src/main/java/com/flipkart/varadhi/entities/VaradhiTopic.java
(1 hunks)entities/src/main/java/com/flipkart/varadhi/entities/auth/ResourceAction.java
(1 hunks)entities/src/test/java/com/flipkart/varadhi/entities/LifecycleStatusTest.java
(1 hunks)entities/src/test/java/com/flipkart/varadhi/entities/ResourceActionRequestTest.java
(1 hunks)entities/src/test/java/com/flipkart/varadhi/entities/VaradhiSubscriptionTest.java
(1 hunks)entities/src/test/java/com/flipkart/varadhi/entities/VaradhiTopicTest.java
(1 hunks)entities/src/test/java/com/flipkart/varadhi/entities/auth/ResourceActionTest.java
(1 hunks)entities/src/testFixtures/java/com/flipkart/varadhi/entities/SubscriptionUtils.java
(5 hunks)producer/src/test/java/com/flipkart/varadhi/services/ProducerServiceTests.java
(3 hunks)pulsar/src/test/java/com/flipkart/varadhi/pulsar/PulsarProducerFactoryTest.java
(1 hunks)pulsar/src/test/java/com/flipkart/varadhi/pulsar/PulsarStackProviderTest.java
(1 hunks)pulsar/src/test/java/com/flipkart/varadhi/pulsar/entities/PulsarProducerTest.java
(1 hunks)pulsar/src/test/java/com/flipkart/varadhi/pulsar/services/PulsarTopicServiceTest.java
(4 hunks)pulsar/src/testE2E/java/com/flipkart/varadhi/pulsar/PulsarTopicServiceTest.java
(2 hunks)server/src/main/java/com/flipkart/varadhi/config/RestOptions.java
(1 hunks)server/src/main/java/com/flipkart/varadhi/services/SubscriptionService.java
(2 hunks)server/src/main/java/com/flipkart/varadhi/services/VaradhiTopicService.java
(1 hunks)server/src/main/java/com/flipkart/varadhi/utils/VaradhiSubscriptionFactory.java
(12 hunks)server/src/main/java/com/flipkart/varadhi/utils/VaradhiTopicFactory.java
(1 hunks)server/src/main/java/com/flipkart/varadhi/web/entities/SubscriptionResource.java
(5 hunks)server/src/main/java/com/flipkart/varadhi/web/entities/TopicResource.java
(1 hunks)server/src/main/java/com/flipkart/varadhi/web/routes/RouteDefinition.java
(1 hunks)server/src/main/java/com/flipkart/varadhi/web/v1/admin/SubscriptionHandlers.java
(3 hunks)server/src/main/java/com/flipkart/varadhi/web/v1/admin/TopicHandlers.java
(4 hunks)server/src/test/java/com/flipkart/varadhi/services/SubscriptionServiceTest.java
(2 hunks)server/src/test/java/com/flipkart/varadhi/services/VaradhiTopicServiceTest.java
(2 hunks)server/src/test/java/com/flipkart/varadhi/utils/VaradhiSubscriptionFactoryTest.java
(1 hunks)server/src/test/java/com/flipkart/varadhi/utils/VaradhiTopicFactoryTest.java
(1 hunks)server/src/test/java/com/flipkart/varadhi/web/WebTestBase.java
(5 hunks)server/src/test/java/com/flipkart/varadhi/web/admin/SubscriptionHandlersTest.java
(2 hunks)server/src/test/java/com/flipkart/varadhi/web/admin/SubscriptionTestBase.java
(2 hunks)server/src/test/java/com/flipkart/varadhi/web/admin/TopicHandlersTest.java
(2 hunks)server/src/test/java/com/flipkart/varadhi/web/entities/SubscriptionResourceTest.java
(1 hunks)server/src/test/java/com/flipkart/varadhi/web/entities/TopicResourceTest.java
(1 hunks)server/src/testE2E/java/com/flipkart/varadhi/AuthZProviderTests.java
(3 hunks)server/src/testE2E/java/com/flipkart/varadhi/E2EBase.java
(8 hunks)server/src/testE2E/java/com/flipkart/varadhi/OrgTests.java
(1 hunks)server/src/testE2E/java/com/flipkart/varadhi/SubscriptionTests.java
(10 hunks)server/src/testE2E/java/com/flipkart/varadhi/TopicTests.java
(5 hunks)
✅ Files skipped from review due to trivial changes (5)
- pulsar/src/test/java/com/flipkart/varadhi/pulsar/entities/PulsarProducerTest.java
- controller/src/test/java/com/flipkart/varadhi/controller/AssignmentManagerTest.java
- controller/src/test/java/com/flipkart/varadhi/controller/OperationMgrTest.java
- controller/src/test/java/com/flipkart/varadhi/controller/ControllerApiMgrTest.java
- pulsar/src/testE2E/java/com/flipkart/varadhi/pulsar/PulsarTopicServiceTest.java
🚧 Files skipped from review as they are similar to previous changes (8)
- entities/src/test/java/com/flipkart/varadhi/entities/VaradhiSubscriptionTest.java
- server/src/main/java/com/flipkart/varadhi/config/RestOptions.java
- server/src/testE2E/java/com/flipkart/varadhi/TopicTests.java
- server/src/testE2E/java/com/flipkart/varadhi/E2EBase.java
- producer/src/test/java/com/flipkart/varadhi/services/ProducerServiceTests.java
- common/src/main/java/com/flipkart/varadhi/Constants.java
- entities/src/main/java/com/flipkart/varadhi/entities/VaradhiSubscription.java
- server/src/test/java/com/flipkart/varadhi/web/admin/SubscriptionTestBase.java
⏰ Context from checks skipped due to timeout of 90000ms (1)
- GitHub Check: build
🔇 Additional comments (60)
pulsar/src/test/java/com/flipkart/varadhi/pulsar/PulsarProducerFactoryTest.java (1)
35-35
: LGTM! Constant naming convention update.The change from
DefaultTopicCapacity
toDEFAULT_TOPIC_CAPACITY
aligns with Java's standard naming convention for constants (uppercase with underscores).server/src/test/java/com/flipkart/varadhi/web/entities/TopicResourceTest.java (2)
1-14
: LGTM! Clean package structure and imports.The package structure follows Java conventions, and imports are well-organized with appropriate static imports for JUnit assertions.
15-15
: LGTM! Test class follows naming conventions.server/src/main/java/com/flipkart/varadhi/utils/VaradhiTopicFactory.java (3)
3-8
: LGTM!The imports are well-organized and all are being utilized in the code.
Also applies to: 12-12
30-44
: LGTM!The constructor documentation and implementation are well-structured and clear.
65-77
: LGTM!The
planDeployment
method is well-documented and properly implements the topic deployment logic.server/src/test/java/com/flipkart/varadhi/web/admin/TopicHandlersTest.java (2)
250-257
: Add negative test cases for restore operation.The restore operation only tests the happy path. Consider adding test cases for:
- Restoring a non-existent topic
- Restoring a topic that wasn't soft-deleted
- Restoring a hard-deleted topic
274-291
: LGTM! Helper methods are well-structured.The helper methods are focused, maintainable, and follow good practices:
- Clear error response assertion
- Consistent URL construction
- Reusable topic resource creation
controller/src/test/java/com/flipkart/varadhi/controller/impl/LeastAssignedStrategyTests.java (5)
26-27
: LGTM! Builder pattern refactoring looks good.The change from
getBuilder()
tobuilder()
follows a cleaner builder pattern implementation while maintaining the test's functionality.
36-36
: LGTM! Builder usage is consistent.The builder pattern change is applied consistently while preserving the test's comprehensive verification of shard assignments and resource management.
55-55
: LGTM! Consistent builder usage in resource constraint test.The builder pattern change is applied correctly in this test case that verifies capacity exception handling.
66-67
: LGTM! Builder chaining with capacity configuration looks good.The builder pattern is effectively used with method chaining to configure both shards and capacity, while maintaining the test's node reuse verification logic.
88-89
: LGTM! Builder usage maintains test clarity.The builder pattern is consistently applied with proper method chaining, preserving the test's verification of round-robin node assignment behavior.
server/src/test/java/com/flipkart/varadhi/utils/VaradhiTopicFactoryTest.java (4)
4-11
: LGTM! Good visibility and import organization.The change to package-private visibility is appropriate for test classes, and the new imports are well-organized and necessary for the lifecycle management functionality.
Also applies to: 29-29
31-39
: LGTM! Good use of constants and Mockito annotations.The introduction of static final constants improves maintainability, and the Mockito annotations are properly used for dependency injection.
46-55
: LGTM! Well-structured test setup.The setup method follows test best practices with proper mock initialization and clear mock behavior setup.
59-88
: Add test cases for soft deletion scenarios.Given that this PR implements soft deletion support, consider adding test cases to verify:
- Topic creation with different lifecycle states
- Validation of soft-deleted topics
- Edge cases around topic restoration
Would you like me to help generate additional test cases for these scenarios?
server/src/test/java/com/flipkart/varadhi/web/WebTestBase.java (5)
44-46
: LGTM! Constants improve code maintainability.The replacement of mutable fields with static final constants enhances code clarity and thread safety.
81-84
: LGTM! Improved error reporting in teardown.The addition of an assertion message helps with debugging when server closure fails.
106-110
: LGTM! Better error handling for unsupported HTTP methods.The use of
UnsupportedOperationException
is more appropriate thanfail()
for handling unsupported HTTP methods.
113-182
: LGTM! Improved method naming and response handling.The changes enhance code clarity through:
- More descriptive method names (e.g.,
sendRequestWithBody
→sendRequestWithEntity
)- Robust null/empty response handling
- Better assertion messages for debugging
223-226
: LGTM! Better response handling with PostResponseCapture.The introduction of
PostResponseCapture
class improves code organization by encapsulating response data.entities/src/main/java/com/flipkart/varadhi/entities/VaradhiTopic.java (2)
20-20
: LGTM! Well-designed status field addition.The new
status
field follows the class's immutable design pattern and properly integrates with Lombok's @Getter annotation.
22-41
: LGTM! Constructor changes are well-documented and maintain encapsulation.The private constructor properly initializes the new status field and includes comprehensive Javadoc.
pulsar/src/test/java/com/flipkart/varadhi/pulsar/services/PulsarTopicServiceTest.java (4)
53-53
: LGTM! Consistent constant naming.The constant reference update follows Java naming conventions and maintains consistency across the codebase.
63-63
: LGTM! Consistent constant naming.The constant reference update maintains consistency with the previous changes.
73-73
: LGTM! Consistent constant naming.The constant reference update maintains consistency with the previous changes.
93-93
: LGTM! Verify constant usage consistency.The constant reference update maintains consistency with the previous changes.
Let's verify that all occurrences of the old constant name have been updated:
✅ Verification successful
Constant Usage Consistency Verified
The updated constant is used consistently in the codebase. The search results confirm that instances referencing
Constants.DEFAULT_TOPIC_CAPACITY
are correct, and the lower-casedefaultTopicCapacity
entries remain only as configuration keys and variable names, which is expected.
PulsarTopicServiceTest.java
and several other test files correctly useConstants.DEFAULT_TOPIC_CAPACITY
.- Other occurrences of
defaultTopicCapacity
are in configuration files or local variable names, not as direct constant references.🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Description: Check for any remaining instances of the old constant name # and verify the new constant is used consistently. echo "Checking for old constant name 'DefaultTopicCapacity'..." rg -i "DefaultTopicCapacity" echo "Verifying usage of new constant name 'DEFAULT_TOPIC_CAPACITY'..." rg "DEFAULT_TOPIC_CAPACITY"Length of output: 5840
server/src/test/java/com/flipkart/varadhi/utils/VaradhiSubscriptionFactoryTest.java (1)
1-23
: LGTM!The file structure, package declaration, imports, and class inheritance are well-organized and appropriate for the test class.
entities/src/test/java/com/flipkart/varadhi/entities/VaradhiTopicTest.java (3)
6-17
: LGTM! Constants follow Java naming conventions.The constant renaming to uppercase and static imports organization look good.
34-55
: LGTM! Comprehensive test coverage for core functionality.The tests effectively validate all essential properties including lifecycle status. Good use of
assertAll
for multiple assertions.
58-109
: Consider adding test for invalid project/topic names.The tests cover the happy path and basic edge cases well. Consider adding tests for invalid project or topic names to ensure proper validation.
Let's check if there are any validation rules for project/topic names:
server/src/main/java/com/flipkart/varadhi/services/VaradhiTopicService.java (3)
3-6
: LGTM! Well-documented class structure.The class structure is well-organized with clear documentation and appropriate imports for the new lifecycle management features.
Also applies to: 18-34
38-51
: Consider implementing partial failure handling.If
storageTopicService.create(...)
succeeds butmetaStore.createTopic(...)
fails, resources may become partially created. Consider implementing a compensating transaction strategy.Run this script to check for existing error handling patterns in the codebase:
#!/bin/bash # Description: Search for error handling patterns in storage topic creation # Test: Look for try-catch blocks or transaction handling around storage topic creation rg -A 10 "storageTopicService.*create.*try" .
122-151
: LGTM! Robust restoration implementation.The restoration logic includes proper permission checks and state validation.
entities/src/main/java/com/flipkart/varadhi/entities/auth/ResourceAction.java (2)
16-21
: Clarify the use ofResourceType.ROOT
for listing organizations
UsingResourceType.ROOT
for theORG_LIST
action (line 20) might be counterintuitive if the rest of the ORG actions useResourceType.ORG
. Verify that the logic for listing organizations through the root resource is intentional and consistent with other resource types.
44-52
: Validate the newRESTORE
,CONSUME
, andPRODUCE
actions
These new actions (lines 49, 50, 51) introduce additional behavior for topic resources. Ensure proper access control checks and that the rest of the codebase consistently handles these actions (e.g., corresponding REST endpoints, service-layer logic).
[approve]server/src/main/java/com/flipkart/varadhi/web/entities/TopicResource.java (2)
33-55
: Validate non-nullactionCode
at construction
The constructor (lines 33–55) introducesactionCode
for eachTopicResource
. If the workflow requires an action code at creation time, consider adding an explicit null check to avoid accidental passing of null, which might lead to ambiguous states later.
88-114
: Ensure consistent round-trip conversions
Thefrom(VaradhiTopic)
method (lines 88–104) andtoVaradhiTopic()
method (lines 108–114) correctly passactionCode
. Double-check that upstream and downstream systems expect the updated code, including possible new states (e.g.,RESTORE
). It may be beneficial to expand unit tests verifying thatactionCode
is preserved through these conversions.
[approve]server/src/main/java/com/flipkart/varadhi/utils/VaradhiSubscriptionFactory.java (2)
Line range hint
65-92
: Confirm usage ofsubscriptionResource.getActionCode()
Theget
method (lines 65–92) now includes the subscription’sactionCode
. Ensure that any subscription-creation flows calling this method provide a valid action code and that the rest of the system (e.g., ACL checks, user notifications) interprets it correctly.
Line range hint
96-125
: Handle edge cases forgetSubscriptionShards
If the computed list of topic partitions (lines 116–125) is empty or null, the current logic proceeds without explicit handling. Consider adding a safeguard to prevent unexpected runtime errors when no partitions are available.entities/src/main/java/com/flipkart/varadhi/entities/ResourceActionRequest.java (1)
11-12
: LGTM! Well-structured record with proper validation.The record is well-designed with appropriate validation annotations and implements the Validatable interface.
server/src/testE2E/java/com/flipkart/varadhi/OrgTests.java (1)
46-46
: LGTM! Improved assertion readability.Good change to use
assertFalse
instead ofassertTrue(!x)
. This makes the test's intent clearer and follows testing best practices.entities/src/test/java/com/flipkart/varadhi/entities/ResourceActionRequestTest.java (1)
12-22
: LGTM! Comprehensive test coverage.The test suite is well-structured with clear test cases covering essential scenarios.
Also applies to: 24-31, 33-40, 42-49, 51-58
entities/src/test/java/com/flipkart/varadhi/entities/LifecycleStatusTest.java (1)
12-21
: LGTM! Well-structured test suite.The test suite provides good coverage of the LifecycleStatus functionality with clear and descriptive test cases.
Also applies to: 23-36, 38-48, 50-60, 62-67
entities/src/test/java/com/flipkart/varadhi/entities/auth/ResourceActionTest.java (1)
1-62
: LGTM! Well-structured test class with comprehensive coverage.The test class effectively validates all aspects of ResourceAction using grouped assertions, which helps identify all potential failures in a single test run.
entities/src/main/java/com/flipkart/varadhi/entities/LifecycleStatus.java (2)
97-101
: Consider implementing finer-grained action codes.The commented-out action codes suggest a more granular approach to action tracking. This could provide better insights into who initiated actions and why. Consider if this granularity would be beneficial for auditing or debugging purposes.
Would you like to proceed with implementing the finer-grained action codes? This could help in:
- Distinguishing between direct user actions and admin-requested actions
- Tracking system-initiated actions separately from admin-forced actions
- Improving audit trails
8-115
: LGTM! Well-structured lifecycle management implementation.The class provides a robust framework for managing entity lifecycles with:
- Clear state transitions with associated messages
- Flexible action code system
- Proper null handling in update methods
- Comprehensive documentation
server/src/main/java/com/flipkart/varadhi/web/entities/SubscriptionResource.java (1)
Line range hint
1-191
: LGTM! Clean integration of lifecycle status.The changes properly integrate the new ActionCode while maintaining clean code principles:
- Comprehensive documentation
- Consistent factory methods
- Proper validation
- Clear separation of concerns
server/src/testE2E/java/com/flipkart/varadhi/SubscriptionTests.java (2)
208-208
: LGTM! Improved error message clarity.The new error message "Grouped subscription cannot be created or updated for a non-grouped topic 'default.topic1'" is more descriptive and user-friendly compared to the previous version.
38-45
: LGTM! Proper integration of lifecycle status and deletion types.The changes correctly integrate:
- LifecycleStatus.ActionCode for resource creation
- Explicit deletion type specification
Also applies to: 91-91, 134-134
entities/src/testFixtures/java/com/flipkart/varadhi/entities/SubscriptionUtils.java (1)
15-18
: LGTM! Well-structured default constants.The default constants are well-named and appropriately scoped, providing clear configuration values for test scenarios.
server/src/testE2E/java/com/flipkart/varadhi/AuthZProviderTests.java (1)
56-59
: LGTM! Clean integration of lifecycle status in test data.The addition of
LifecycleStatus.ActionCode.SYSTEM_ACTION
to test data setup is appropriate and maintains test coverage for the new feature.server/src/main/java/com/flipkart/varadhi/services/SubscriptionService.java (1)
211-237
: Enhance validation for restoration prerequisites.The restoration logic should validate the topic's existence and state before proceeding with subscription restoration.
server/src/main/java/com/flipkart/varadhi/web/v1/admin/SubscriptionHandlers.java (3)
4-11
: LGTM! Well-structured imports and clear class documentation.The new imports support the lifecycle management features, and the class documentation clearly describes its responsibilities.
Also applies to: 53-56
70-78
: LGTM! Constructor and route definitions are well-structured.The constructor initialization is cleaner, and the new restore route is properly integrated with appropriate authorization.
Also applies to: 133-137
365-369
: Consider adding tests for edge cases in lifecycle operations.While the implementation of helper methods is solid, consider adding tests for:
- Concurrent lifecycle operations
- Race conditions in state transitions
- Edge cases in validation logic
The past review comments already identified this gap. Adding these tests will improve the robustness of the implementation.
Also applies to: 460-466
server/src/test/java/com/flipkart/varadhi/services/VaradhiTopicServiceTest.java (1)
136-143
: Add test for hard deleting a soft-deleted topic.The deletion tests don't cover the scenario of attempting to hard delete a topic that's already soft-deleted. This is an important state transition to verify.
The past review comments already identified this gap. The suggested test implementation in the past review is appropriate and matches the service's behavior.
server/src/test/java/com/flipkart/varadhi/web/admin/SubscriptionHandlersTest.java (1)
119-130
: LGTM! Comprehensive test coverage with clear test cases.The test implementation:
- Uses descriptive test method names
- Covers both success and failure scenarios
- Includes proper assertions and verifications
Also applies to: 298-313, 482-495
server/src/test/java/com/flipkart/varadhi/services/SubscriptionServiceTest.java (1)
896-900
: Enhance subscription comparison in assertSubscriptionsEqual.The helper method doesn't compare all relevant fields of the subscription. Consider adding comparisons for:
- Endpoint configuration
- Retry policy
- Consumption policy
- Version
- Status
The past review comments already identified this gap. The suggested implementation in the past review would improve the test's reliability.
@Test | ||
void from_CreatesSubscriptionResourceFromVaradhiSubscription() { | ||
VaradhiSubscription varadhiSubscription = createUngroupedSubscription( | ||
"subscriptionName", PROJECT, VaradhiTopic.of( | ||
"project1.topic1", "topic1", false, Constants.DEFAULT_TOPIC_CAPACITY, | ||
LifecycleStatus.ActionCode.SYSTEM_ACTION | ||
) | ||
); | ||
SubscriptionResource subscriptionResource = SubscriptionResource.from(varadhiSubscription); | ||
assertAll( | ||
() -> assertEquals("subscriptionName", subscriptionResource.getName()), | ||
() -> assertEquals(PROJECT.getName(), subscriptionResource.getProject()), | ||
() -> assertEquals("topic1", subscriptionResource.getTopic()), | ||
() -> assertEquals("project1", subscriptionResource.getTopicProject()), | ||
() -> assertEquals(varadhiSubscription.getDescription(), subscriptionResource.getDescription()), | ||
() -> assertFalse(subscriptionResource.isGrouped()), | ||
() -> assertNotNull(subscriptionResource.getEndpoint()), | ||
() -> assertNotNull(subscriptionResource.getRetryPolicy()), | ||
() -> assertNotNull(subscriptionResource.getConsumptionPolicy()), | ||
() -> assertNotNull(subscriptionResource.getProperties()), | ||
() -> assertEquals(LifecycleStatus.ActionCode.SYSTEM_ACTION, subscriptionResource.getActionCode()) | ||
); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add test cases for soft-deletion scenarios.
Given that this PR implements soft-deletion support, consider adding test cases for:
- Converting a soft-deleted
VaradhiSubscription
toSubscriptionResource
- Different lifecycle status states and action codes
- Restoration scenarios
This will ensure the new soft-deletion functionality is properly tested.
class SubscriptionResourceTest extends SubscriptionTestBase { | ||
|
||
@Test | ||
void of_CreatesSubscriptionResource() { | ||
SubscriptionResource subscriptionResource = createSubscriptionResource( | ||
"subscriptionName", PROJECT, TOPIC_RESOURCE | ||
); | ||
assertAll( | ||
() -> assertEquals("subscriptionName", subscriptionResource.getName()), | ||
() -> assertEquals(PROJECT.getName(), subscriptionResource.getProject()), | ||
() -> assertEquals(TOPIC_RESOURCE.getName(), subscriptionResource.getTopic()), | ||
() -> assertEquals(TOPIC_RESOURCE.getProject(), subscriptionResource.getTopicProject()), | ||
() -> assertEquals("Description", subscriptionResource.getDescription()), | ||
() -> assertFalse(subscriptionResource.isGrouped()), | ||
() -> assertNotNull(subscriptionResource.getEndpoint()), | ||
() -> assertNotNull(subscriptionResource.getRetryPolicy()), | ||
() -> assertNotNull(subscriptionResource.getConsumptionPolicy()), | ||
() -> assertNotNull(subscriptionResource.getProperties()), | ||
() -> assertEquals(LifecycleStatus.ActionCode.SYSTEM_ACTION, subscriptionResource.getActionCode()) | ||
); | ||
} | ||
|
||
@Test | ||
void from_CreatesSubscriptionResourceFromVaradhiSubscription() { | ||
VaradhiSubscription varadhiSubscription = createUngroupedSubscription( | ||
"subscriptionName", PROJECT, VaradhiTopic.of( | ||
"project1.topic1", "topic1", false, Constants.DEFAULT_TOPIC_CAPACITY, | ||
LifecycleStatus.ActionCode.SYSTEM_ACTION | ||
) | ||
); | ||
SubscriptionResource subscriptionResource = SubscriptionResource.from(varadhiSubscription); | ||
assertAll( | ||
() -> assertEquals("subscriptionName", subscriptionResource.getName()), | ||
() -> assertEquals(PROJECT.getName(), subscriptionResource.getProject()), | ||
() -> assertEquals("topic1", subscriptionResource.getTopic()), | ||
() -> assertEquals("project1", subscriptionResource.getTopicProject()), | ||
() -> assertEquals(varadhiSubscription.getDescription(), subscriptionResource.getDescription()), | ||
() -> assertFalse(subscriptionResource.isGrouped()), | ||
() -> assertNotNull(subscriptionResource.getEndpoint()), | ||
() -> assertNotNull(subscriptionResource.getRetryPolicy()), | ||
() -> assertNotNull(subscriptionResource.getConsumptionPolicy()), | ||
() -> assertNotNull(subscriptionResource.getProperties()), | ||
() -> assertEquals(LifecycleStatus.ActionCode.SYSTEM_ACTION, subscriptionResource.getActionCode()) | ||
); | ||
} | ||
|
||
@Test | ||
void buildInternalName_ReturnsCorrectInternalName() { | ||
String internalName = SubscriptionResource.buildInternalName(PROJECT.getName(), "subscriptionName"); | ||
assertEquals("project1.subscriptionName", internalName); | ||
} | ||
|
||
@Test | ||
void getSubscriptionInternalName_ReturnsCorrectInternalName() { | ||
SubscriptionResource subscriptionResource = createSubscriptionResource( | ||
"subscriptionName", PROJECT, TOPIC_RESOURCE | ||
); | ||
assertEquals("project1.subscriptionName", subscriptionResource.getSubscriptionInternalName()); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add test methods for soft-deletion functionality.
To align with the PR objectives for implementing soft-deletion support, consider adding the following test methods:
testSoftDeletionState
: Verify the soft-deletion state transitionstestHardDeletionState
: Verify the hard-deletion state transitionstestRestorationFlow
: Verify the restoration processtestInvalidStateTransitions
: Verify that invalid state transitions are prevented
This will ensure comprehensive test coverage for the new lifecycle management features.
@@ -74,7 +74,7 @@ public void testGetStorageTopicFactory_NotInitialized() { | |||
@Test | |||
public void testGetStorageTopicFactory_Initialized() { | |||
String topicName = "foobar"; | |||
TopicCapacityPolicy capacity = Constants.DefaultTopicCapacity; | |||
TopicCapacityPolicy capacity = Constants.DEFAULT_TOPIC_CAPACITY; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Codebase verification
Action Required: Address Inconsistent Constant Naming
- The updated constant in
pulsar/src/test/java/com/flipkart/varadhi/pulsar/PulsarStackProviderTest.java
now correctly usesConstants.DEFAULT_TOPIC_CAPACITY
. - However, the search revealed that
server/src/main/java/com/flipkart/varadhi/verticles/webserver/WebServerVerticle.java
still references the old constantDefaultTopicCapacity
. This inconsistency could lead to maintenance issues or unintended behavior.
Please update the constant usage in the production file to align with the new naming convention.
🔗 Analysis chain
LGTM! Constant reference updated correctly.
The constant reference has been updated from DefaultTopicCapacity
to DEFAULT_TOPIC_CAPACITY
to align with the new naming convention in the Constants
class.
Let's verify that this constant has been consistently updated across the codebase:
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Check for any remaining references to the old constant name
# and verify the new constant is used consistently.
echo "Checking for old constant references..."
rg "DefaultTopicCapacity" -l
echo -e "\nVerifying new constant usage..."
rg "DEFAULT_TOPIC_CAPACITY" -l
Length of output: 1401
@Test | ||
void getShardCapacity_ValidInput_ReturnsCorrectCapacity() throws Exception { | ||
Method method = | ||
VaradhiSubscriptionFactory.class.getDeclaredMethod( | ||
"getShardCapacity", TopicCapacityPolicy.class, | ||
int.class | ||
); | ||
method.setAccessible(true); | ||
|
||
TopicCapacityPolicy result = | ||
(TopicCapacityPolicy) method.invoke(varadhiSubscriptionFactory, CAPACITY_POLICY, 2); | ||
|
||
assertNotNull(result); | ||
assertEquals(CAPACITY_POLICY.getReadFanOut(), result.getReadFanOut()); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider testing through public interfaces instead of using reflection.
Testing private methods through reflection might indicate that these methods should be extracted into a separate utility class or that we should test the behavior through public methods instead.
@Test | ||
void getShardMainSubName_ValidInput_ReturnsCorrectName() throws Exception { | ||
Method method = | ||
VaradhiSubscriptionFactory.class.getDeclaredMethod("getShardMainSubName", String.class, int.class); | ||
method.setAccessible(true); | ||
|
||
String result = (String) method.invoke(varadhiSubscriptionFactory, SUBSCRIPTION_NAME, 0); | ||
|
||
assertEquals("testSubscription.shard.0.MAIN", result); | ||
} | ||
|
||
@Test | ||
void getInternalSubName_ValidInput_ReturnsCorrectName() throws Exception { | ||
Method method = | ||
VaradhiSubscriptionFactory.class.getDeclaredMethod( | ||
"getInternalSubName", String.class, int.class, | ||
InternalQueueCategory.class, int.class | ||
); | ||
method.setAccessible(true); | ||
|
||
String result = | ||
(String) method.invoke( | ||
varadhiSubscriptionFactory, SUBSCRIPTION_NAME, 0, | ||
InternalQueueCategory.MAIN, 0 | ||
); | ||
|
||
assertEquals("is-testSubscription.shard-0.MAIN-0", result); | ||
} | ||
|
||
@Test | ||
void getInternalTopicName_ValidInput_ReturnsCorrectName() throws Exception { | ||
Method method = | ||
VaradhiSubscriptionFactory.class.getDeclaredMethod( | ||
"getInternalTopicName", String.class, int.class, | ||
InternalQueueCategory.class, int.class | ||
); | ||
method.setAccessible(true); | ||
|
||
String result = | ||
(String) method.invoke( | ||
varadhiSubscriptionFactory, SUBSCRIPTION_NAME, 0, | ||
InternalQueueCategory.MAIN, 0 | ||
); | ||
|
||
assertEquals("it-testSubscription.shard-0.MAIN-0", result); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Enhance test coverage with additional scenarios.
The current tests only cover the happy path. Consider adding tests for:
- Edge cases (empty strings, null values, negative numbers)
- Error scenarios
- The public methods that use these private methods
Also, consider using BDD-style naming for better readability:
-void getShardMainSubName_ValidInput_ReturnsCorrectName()
+void shouldGenerateCorrectShardMainSubNameWhenGivenValidInput()
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
@Test | |
void getShardMainSubName_ValidInput_ReturnsCorrectName() throws Exception { | |
Method method = | |
VaradhiSubscriptionFactory.class.getDeclaredMethod("getShardMainSubName", String.class, int.class); | |
method.setAccessible(true); | |
String result = (String) method.invoke(varadhiSubscriptionFactory, SUBSCRIPTION_NAME, 0); | |
assertEquals("testSubscription.shard.0.MAIN", result); | |
} | |
@Test | |
void getInternalSubName_ValidInput_ReturnsCorrectName() throws Exception { | |
Method method = | |
VaradhiSubscriptionFactory.class.getDeclaredMethod( | |
"getInternalSubName", String.class, int.class, | |
InternalQueueCategory.class, int.class | |
); | |
method.setAccessible(true); | |
String result = | |
(String) method.invoke( | |
varadhiSubscriptionFactory, SUBSCRIPTION_NAME, 0, | |
InternalQueueCategory.MAIN, 0 | |
); | |
assertEquals("is-testSubscription.shard-0.MAIN-0", result); | |
} | |
@Test | |
void getInternalTopicName_ValidInput_ReturnsCorrectName() throws Exception { | |
Method method = | |
VaradhiSubscriptionFactory.class.getDeclaredMethod( | |
"getInternalTopicName", String.class, int.class, | |
InternalQueueCategory.class, int.class | |
); | |
method.setAccessible(true); | |
String result = | |
(String) method.invoke( | |
varadhiSubscriptionFactory, SUBSCRIPTION_NAME, 0, | |
InternalQueueCategory.MAIN, 0 | |
); | |
assertEquals("it-testSubscription.shard-0.MAIN-0", result); | |
} | |
@Test | |
void shouldGenerateCorrectShardMainSubNameWhenGivenValidInput() throws Exception { | |
Method method = | |
VaradhiSubscriptionFactory.class.getDeclaredMethod("getShardMainSubName", String.class, int.class); | |
method.setAccessible(true); | |
String result = (String) method.invoke(varadhiSubscriptionFactory, SUBSCRIPTION_NAME, 0); | |
assertEquals("testSubscription.shard.0.MAIN", result); | |
} | |
@Test | |
void getInternalSubName_ValidInput_ReturnsCorrectName() throws Exception { | |
Method method = | |
VaradhiSubscriptionFactory.class.getDeclaredMethod( | |
"getInternalSubName", String.class, int.class, | |
InternalQueueCategory.class, int.class | |
); | |
method.setAccessible(true); | |
String result = | |
(String) method.invoke( | |
varadhiSubscriptionFactory, SUBSCRIPTION_NAME, 0, | |
InternalQueueCategory.MAIN, 0 | |
); | |
assertEquals("is-testSubscription.shard-0.MAIN-0", result); | |
} | |
@Test | |
void getInternalTopicName_ValidInput_ReturnsCorrectName() throws Exception { | |
Method method = | |
VaradhiSubscriptionFactory.class.getDeclaredMethod( | |
"getInternalTopicName", String.class, int.class, | |
InternalQueueCategory.class, int.class | |
); | |
method.setAccessible(true); | |
String result = | |
(String) method.invoke( | |
varadhiSubscriptionFactory, SUBSCRIPTION_NAME, 0, | |
InternalQueueCategory.MAIN, 0 | |
); | |
assertEquals("it-testSubscription.shard-0.MAIN-0", result); | |
} |
// TODO: Improve efficiency by avoiding a full scan of all subscriptions across projects. | ||
List<VaradhiSubscription> subscriptions = metaStore.getAllSubscriptionNames().stream() | ||
.map(metaStore::getSubscription) | ||
.filter(subscription -> subscription.getTopic().equals(topicName)) | ||
.toList(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Codebase verification
🛠️ Refactor suggestion
Subscription Retrieval Efficiency Issue
The investigation confirms that the current implementation in VaradhiTopicService.java
relies on metaStore.getAllSubscriptionNames()
and subsequently filters subscriptions in memory. The MetaStore interface only offers a method to retrieve all subscription names (as seen in spi/src/main/java/com/flipkart/varadhi/spi/db/MetaStore.java
) without any specialized filtering method (e.g., getSubscriptionsByTopic
).
- The only available method in MetaStore is
getAllSubscriptionNames()
, and there is no direct query for subscriptions by topic. - The code in
VaradhiTopicService.java
loads all subscriptions into memory and filters them, which may lead to performance issues for large datasets. - To improve efficiency, consider implementing a dedicated query method in MetaStore (or its underlying datastore) that filters subscriptions by topic directly.
🔗 Analysis chain
Optimize subscription validation performance.
The current implementation loads all subscriptions into memory. Consider adding an index or a direct query method in the MetaStore.
Let's check if there are more efficient patterns in use:
We need to gather additional context before finalizing the recommendation. Let's run a script that searches for any alternative subscription filtering methods in the codebase (e.g. a method like getSubscriptionsByTopic
), list usages of getAllSubscriptionNames()
, and show the definitions of the MetaStore
interface. This will help verify whether we have efficient query methods available or if a new solution is required.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Search for subscription querying patterns
# Test: Look for existing subscription filtering methods in MetaStore
ast-grep --pattern 'interface MetaStore {
$$$
$ret getSubscriptions$_($$$) {
$$$
}
$$$
}'
Length of output: 103
Script:
#!/bin/bash
echo "Searching for direct subscription filtering methods (e.g. getSubscriptionsByTopic) in the codebase..."
rg "getSubscriptionsByTopic" -n .
echo "Searching for usages of getAllSubscriptionNames()..."
rg "getAllSubscriptionNames" -n .
echo "Listing definitions related to the MetaStore interface..."
rg "interface MetaStore" -n .
Length of output: 2145
public void listTopics(RoutingContext ctx) { | ||
String projectName = ctx.pathParam(PATH_PARAM_PROJECT); | ||
List<String> varadhiTopics = varadhiTopicService.getVaradhiTopics(projectName); | ||
|
||
String projectPrefixOfVaradhiTopic = projectName + NAME_SEPARATOR; | ||
List<String> topicResourceNames = new ArrayList<>(); | ||
varadhiTopics.forEach(varadhiTopic -> { | ||
if (varadhiTopic.startsWith(projectPrefixOfVaradhiTopic)) { | ||
String[] splits = varadhiTopic.split(NAME_SEPARATOR_REGEX); | ||
topicResourceNames.add(splits[1]); | ||
} | ||
} | ||
); | ||
ctx.endApiWithResponse(topicResourceNames); | ||
boolean includeInactive = ctx.queryParam(QUERY_PARAM_INCLUDE_INACTIVE).stream() | ||
.findFirst() | ||
.map(Boolean::parseBoolean) | ||
.orElse(false); | ||
|
||
List<String> topics = varadhiTopicService.getVaradhiTopics(projectName, includeInactive).stream() | ||
.filter(topic -> topic.startsWith(projectName + NAME_SEPARATOR)) | ||
.map(topic -> topic.split(NAME_SEPARATOR_REGEX)[1]) | ||
.toList(); | ||
|
||
ctx.endApiWithResponse(topics); | ||
} | ||
|
||
/** | ||
* Retrieves the full topic name from the routing context. | ||
* | ||
* @param ctx the routing context | ||
* | ||
* @return the full topic name | ||
*/ | ||
private String getVaradhiTopicName(RoutingContext ctx) { | ||
String projectName = ctx.pathParam(PATH_PARAM_PROJECT); | ||
String topicResourceName = ctx.pathParam(PATH_PARAM_TOPIC); | ||
return String.join(NAME_SEPARATOR, projectName, topicResourceName); | ||
String topicName = ctx.pathParam(PATH_PARAM_TOPIC); | ||
return buildTopicName(projectName, topicName); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Codebase verification
Enhance Restoration Error Handling in VaradhiTopicService
The current restoration logic only verifies if the topic is already active before proceeding. There are no safeguards against concurrent restoration requests or explicit checks to ensure that a topic exists before attempting restoration. These aspects leave room for race conditions and potential null pointer exceptions.
- File: server/src/main/java/com/flipkart/varadhi/services/VaradhiTopicService.java (method: restore)
- Issue: No explicit handling to prevent concurrent restoration attempts, nor validation on topic existence beyond a simple state check.
🔗 Analysis chain
Enhance error handling for restoration edge cases.
The restoration logic handles basic cases well, but consider adding checks for:
- Topic existence/state
- Concurrent restoration attempts
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Check for potential concurrent restoration issues
ast-grep --pattern 'restore($_, $_) {
$$$
if (!$_.isActive()) {
$$$
}
}'
Length of output: 84
Script:
#!/bin/bash
# Search for any occurrences of a restoration-related method (e.g., "restore(") in Java files.
rg "restore\\(" -t java
Length of output: 1848
Script:
#!/bin/bash
# Extract the restore method implementation from TopicHandlers.java with context.
rg -n "public void restore\\(RoutingContext ctx\\)" -t java server/src/main/java/com/flipkart/varadhi/web/v1/admin/TopicHandlers.java -A 40 -B 10
Length of output: 2085
Script:
#!/bin/bash
# Extract the restore method implementation from VaradhiTopicService.java with context.
rg -n "public void restore\\(" -t java server/src/main/java/com/flipkart/varadhi/services/VaradhiTopicService.java -A 40 -B 10
Length of output: 2552
Description [#215]
This PR introduces support for soft and hard deletion for Varadhi resources, enabling flexible lifecycle management for topics and subscriptions. It includes updates to the service layer, deletion handler, restoration process, and unit tests to ensure a robust and maintainable implementation.
Key Changes
1. Soft Deletion
Added a state to resource entities to indicate whether a topic/subscription is deleted.
Updated the service layer to include methods for:
Modified RESTful operations to check the deletion state before proceeding, ensuring inactive resources are not used.
2. Hard Deletion
3. Restoration Handler
4. Query Parameter in Deletion Handler
SOFT_DELETE
orHARD_DELETE
), allowing users to choose the type of deletion during a delete request.Additional Enhancements
1. Refactored Constants
2. Unit Tests
VaradhiTopicService:
VaradhiTopic:
VaradhiTopicHandler:
Ensured correct handling of topic-related requests.
Refactored test cases to:
Cover edge cases, including:
Work in Progress (WIP) & Open Points
The following items are under development or require discussion:
1. Unit Tests for Subscriptions:
2. State Progression in Producer/Consumer Path:
3. SLA-Based Hard Deletion:
Type of Change
Testing and Validation
Added comprehensive unit tests to verify the correctness of the implementation.
Included edge cases to ensure robust behavior, such as:
Refactored test code for improved maintainability and readability.
Checklist:
Summary by CodeRabbit
Based on the comprehensive summary of changes, here are the updated release notes:
New Features
LifecycleStatus
class to encapsulate lifecycle states and action codes.ResourceActionRequest
record for action requests on resources.Improvements
SubscriptionResource
andTopicResource
to include action codes.Bug Fixes
Technical Enhancements
These release notes provide a high-level overview of the significant changes while maintaining confidentiality about internal implementation details.