Skip to content
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

Open
wants to merge 7 commits into
base: master
Choose a base branch
from

Conversation

devanshu-garg-IN
Copy link
Collaborator

@devanshu-garg-IN devanshu-garg-IN commented Jan 27, 2025

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:

    • Soft deletion: Marking a resource as inactive.
    • Restoration: Restoring soft-deleted resources.
  • Modified RESTful operations to check the deletion state before proceeding, ensuring inactive resources are not used.

2. Hard Deletion

  • Implemented a method in the service layer to permanently delete resources from the database.

3. Restoration Handler

  • Added a new handler to allow users to restore soft-deleted topics, providing flexibility in resource management.

4. Query Parameter in Deletion Handler

  • Enhanced the deletion handler to accept a query parameter (SOFT_DELETE or HARD_DELETE), allowing users to choose the type of deletion during a delete request.

Additional Enhancements

1. Refactored Constants

  • Added Javadoc comments to the Constants class and its inner classes for better documentation.
  • Added private constructors to inner classes to prevent instantiation.

2. Unit Tests

  • VaradhiTopicService:

    • Added tests for topic creation, deletion (soft and hard), restoration, and existence checks.
  • VaradhiTopic:

    • Verified resource creation and state updates.
  • VaradhiTopicHandler:

    • Ensured correct handling of topic-related requests.

    • Refactored test cases to:

      • Reduce code duplication using helper methods for common setup logic.
    • Cover edge cases, including:

      • Filtering out inactive topics.
      • Handling meta store failures.

Work in Progress (WIP) & Open Points

The following items are under development or require discussion:

1. Unit Tests for Subscriptions:

  • Adding tests for VaradhiSubscriptionService, SubscriptionHandlers, and VaradhiSubscription.

2. State Progression in Producer/Consumer Path:

  • Addressing internal cache invalidation to handle topic/subscription deletion scenarios consistently across all pods/services.

3. SLA-Based Hard Deletion:

  • Proposing an SLA-based mechanism with user alerts at fixed intervals before permanent deletion.

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality not to work as expected)
  • This change requires a documentation update

Testing and Validation

  • Added comprehensive unit tests to verify the correctness of the implementation.

  • Included edge cases to ensure robust behavior, such as:

    • Proper handling of inactive resources.
    • Managing failures in the meta store.
  • Refactored test code for improved maintainability and readability.

Checklist:

  • My code follows the style guidelines of this project
  • I have performed a self-review of my code
  • I have commented on my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • Any dependent changes have been merged and published in downstream modules

Summary by CodeRabbit

Based on the comprehensive summary of changes, here are the updated release notes:

  • New Features

    • Added support for soft and hard deletion of topics and subscriptions.
    • Introduced topic and subscription status management (active/inactive).
    • Added topic and subscription restoration capabilities.
    • Enhanced resource management with new deletion type handling.
    • Introduced new methods for managing subscription states and validations.
    • New LifecycleStatus class to encapsulate lifecycle states and action codes.
    • Added ResourceActionRequest record for action requests on resources.
  • Improvements

    • Improved validation and error handling across services.
    • Refined logging and error messages for clarity.
    • Enhanced method naming and code structure for better readability.
    • Added more comprehensive test coverage for various scenarios.
    • Updated SubscriptionResource and TopicResource to include action codes.
  • Bug Fixes

    • Improved handling of resource states and transitions.
    • Fixed potential edge cases in resource management.
  • Technical Enhancements

    • Updated constant naming conventions for consistency.
    • Refactored service and handler classes for better maintainability.
    • Added more robust input validation mechanisms.

These release notes provide a high-level overview of the significant changes while maintaining confidentiality about internal implementation details.

- 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.
@devanshu-garg-IN devanshu-garg-IN added this to the Produce milestone Jan 27, 2025
@devanshu-garg-IN devanshu-garg-IN self-assigned this Jan 27, 2025
@devanshu-garg-IN devanshu-garg-IN linked an issue Jan 27, 2025 that may be closed by this pull request
Copy link

coderabbitai bot commented Jan 27, 2025

Walkthrough

This 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

File Change Summary
common/src/main/java/com/flipkart/varadhi/Constants.java Added new constants, nested classes, and improved documentation for constants. Renamed and updated existing constants for consistency.
entities/src/main/java/com/flipkart/varadhi/entities/ResourceDeletionType.java Introduced new enum for deletion types with conversion method.
entities/src/main/java/com/flipkart/varadhi/entities/VaradhiSubscription.java Enhanced status management, validation, and state transition methods.
entities/src/main/java/com/flipkart/varadhi/entities/VaradhiTopic.java Added status tracking and management methods.
server/src/main/java/com/flipkart/varadhi/services/SubscriptionService.java Updated to support soft/hard deletion, restoration, and improved validation.
server/src/main/java/com/flipkart/varadhi/services/VaradhiTopicService.java Enhanced functionality for managing topics, including deletion and restoration.
server/src/test/java/com/flipkart/varadhi/services/SubscriptionServiceTest.java Refactored tests to cover new functionality and improved naming conventions.
server/src/test/java/com/flipkart/varadhi/services/VaradhiTopicServiceTest.java Updated tests to reflect changes in topic management and lifecycle status.
server/src/testE2E/java/com/flipkart/varadhi/SubscriptionTests.java Updated end-to-end tests to include deletion type parameters and refined error messages.

Possibly related issues

  • Issue Add soft-deletion support for Varadhi Resources #215: Add soft-deletion support for Varadhi Resources
    • This PR directly addresses the need for lifecycle-related fields and soft-deletion support.
    • Introduces ResourceDeletionType enum and status tracking for topics and subscriptions.
    • Provides mechanisms for soft and hard deletion of resources.

Poem

🐰 Soft delete, hard delete, what's the way?
Resources dance in a coding ballet
Status tracked with meticulous care
Rabbit's magic makes deletion fair!
Varadhi's resources now shine bright 🌟

✨ Finishing Touches
  • 📝 Generate Docstrings (Beta)

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?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

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)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a 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 since TopicCapacityPolicy 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 to setupMockTopics.

The method assumes that topicNames and topicStatuses lists have the same size. Add validation to prevent potential IndexOutOfBoundsException.

 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
Throwing ResourceNotFoundException 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 new ResourceDeletionType 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
Reverting grouped 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 to projectService.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
Uses varadhiSubscriptionFactory 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:

  1. Using a constant for the default value
  2. 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:

  1. Non-existent topic
  2. Invalid topic name format
  3. 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:

  1. Restoring a non-existent topic
  2. Restoring an active topic
  3. 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:

  1. Adding a helper method for deletion operations:
private CompletableFuture<Void> deleteSubscription(String name, ResourceDeletionType deletionType) {
    return subscriptionService.deleteSubscription(
        name, o1t1p1, requestedBy, deletionType);
}
  1. 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

📥 Commits

Reviewing files that changed from the base of the PR and between e57a99d and 2b28458.

📒 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.java

Length 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
Declaring storageTopicService and metaStore 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
The exists and getVaradhiTopics 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 by isActiveOrWellProvisionedByName is intuitive for returning valid subscriptions.


54-61: Retrieval approach
The method delegates to getValidatedSubscription for streamlined error handling.


93-112: Start/Stop subscription
Delegating to performSubscriptionOperation 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
Using QUERY_PARAM_DELETION_TYPE and QUERY_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
Adding RestoreSubscription route aligns well with soft deletion. Overall route structure remains consistent.


192-196: Get endpoint
Creates a SubscriptionResource from a validated subscription. Straightforward flow.


222-241: Update endpoint
The TODO 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 to subscriptionService 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
Restricting ignoreConstraints 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:

  1. Vertx ValidationHandlers for request body validation
  2. 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:

  1. Soft deletion of a subscription
  2. Restoration of a soft-deleted subscription
  3. 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, but SubscriptionServiceTest lacks test coverage for these scenarios. Following the patterns established in VaradhiTopicServiceTest, add test cases for:

  1. Soft deletion of a subscription
  2. Successful restoration of a soft-deleted subscription
  3. 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

Comment on lines +19 to +20
// 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
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

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:

  1. Using a different prefix that won't be forwarded
  2. Implementing header filtering before forwarding
  3. Documenting this limitation in security documentation

Comment on lines 123 to 160
@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());
}
Copy link

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

Comment on lines +35 to +48
/**
* 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)
)
);
Copy link

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.

Comment on lines +99 to +113
/**
* 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)
)
);
Copy link

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.

Comment on lines 305 to 325
/**
* 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;
}
}
Copy link

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.
Copy link

@coderabbitai coderabbitai bot left a 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:

  1. Adding a PENDING_DELETION state to support graceful deletion process
  2. 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.

  1. This is another occurrence of the "HARD_DELETE" string literal that should use the suggested constant.
  2. 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

📥 Commits

Reviewing files that changed from the base of the PR and between 2b28458 and bbb8565.

📒 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:

  1. Test soft deletion of a subscription
  2. Test restoration of a soft-deleted subscription
  3. Test operations on soft-deleted subscriptions (should fail)
  4. Test hard deletion after soft deletion
  5. 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.

  1. The query parameter name "deletionType" should be extracted as a constant for consistency.
  2. 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 suggestion

Add test coverage for soft deletion scenarios.

While the hard deletion tests are in place, there are several missing test scenarios that should be covered:

  1. Soft deletion of topics
  2. Restoration of soft-deleted topics
  3. Validation of invalid deletion types
  4. 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:

  1. Soft deletion marks the subscription as deleted but keeps it retrievable
  2. Restoration of a soft-deleted subscription
  3. 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 to SOFT_DELETE) and SubscriptionService 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 java

Length 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 java

Length of output: 9001

Copy link

github-actions bot commented Jan 27, 2025

Test Results

409 tests   409 ✅  57s ⏱️
 64 suites    0 💤
 64 files      0 ❌

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.
Copy link

@coderabbitai coderabbitai bot left a 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:

  1. Produce attempt to a soft-deleted topic
  2. Produce attempt during topic restoration
  3. 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:

  1. Success cases: 10 bytes (well below limit), 19 bytes (just below limit), 20 bytes (at limit)
  2. 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 reproducibility

This ensures that the random values generated in createDlqMessage and generateRandomBytes 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:

  1. Integer overflow/underflow
  2. Empty string value
  3. 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:

  1. setupInfrastructure() for ZK, MetaStore, and service initialization
  2. setupTestData() for creating test entities
  3. 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:

  1. Attempting to delete an already soft-deleted subscription
  2. Attempting to delete a subscription with active consumers
  3. 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:

  1. Extracting route paths into constants to avoid magic strings
  2. 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:

  1. HTTP response headers verification
  2. Error message format consistency checks
  3. 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:

  1. Concurrent deletion attempts
  2. Verification of related resource cleanup
  3. 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:

  1. Version conflict scenarios
  2. Partial update capabilities
  3. 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:

  1. Add JavaDoc comments describing test scenarios and edge cases
  2. Implement test data builders to reduce duplication and improve maintainability
  3. 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 states

The setupSubscriptionForGetMessages helper method should be enhanced to:

  1. Support creation of soft-deleted subscriptions
  2. 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 functionality

While 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:

  1. Lifecycle tests for soft-deleted subscriptions
  2. DLQ operations on restored subscriptions
  3. 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

📥 Commits

Reviewing files that changed from the base of the PR and between bbb8565 and 58c48c1.

📒 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 to sendRequestWithPayload 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 immutability

Also applies to: 44-44


230-231: Consider addressing the TODO comments before merging.

The comments indicate potential improvements:

  1. Splitting into separate update APIs
  2. 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:

  • sendRequestWithBodysendRequestWithEntity
  • sendRequestWithoutBodysendRequestWithoutPayload

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:

  • getSubscriptionResourcecreateSubscriptionResource
  • getUngroupedSubscriptioncreateUngroupedSubscription
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.java

Length of output: 106


Script:

#!/bin/bash
# View the entire WebTestBase.java file
cat server/src/test/java/com/flipkart/varadhi/web/WebTestBase.java

Length of output: 9205

server/src/test/java/com/flipkart/varadhi/services/DlqServiceTest.java (1)

83-83: Add test coverage for soft-deleted subscriptions

The test should verify DLQ message retrieval behavior when:

  1. The subscription is soft-deleted
  2. The subscription is restored from soft-deletion

Comment on lines 231 to 238
@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());
Copy link

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:

  1. Restoring a non-existent topic
  2. Restoring a topic that wasn't soft-deleted
  3. 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");
}

Comment on lines 183 to 195
@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());
}
Copy link

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:

  1. Verify that restoration is only possible from INACTIVE state
  2. 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());
}

Comment on lines +785 to +789
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());
Copy link

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:

  1. Endpoint configuration
  2. Retry policy
  3. Consumption policy
  4. Version
  5. 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.

Suggested change
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());
}

Comment on lines 427 to 472
@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());
}
Copy link

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:

  1. Error scenarios (e.g., restoring an active subscription)
  2. Invalid state transitions
  3. 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());
}

Comment on lines +67 to +68
VaradhiTopic vTopic = TOPIC_RESOURCE.toVaradhiTopic();
VaradhiSubscription subscription = spy(createUngroupedSubscription("sub12", PROJECT, vTopic));
Copy link

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:

  1. Include specific error messages for deletion states
  2. 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.

Comment on lines +47 to +48
VaradhiTopic vTopic = TOPIC_RESOURCE.toVaradhiTopic();
VaradhiSubscription subscription = spy(createUngroupedSubscription("sub12", PROJECT, vTopic));
Copy link

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

Copy link

@coderabbitai coderabbitai bot left a 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:

  1. Provide more descriptive error messages including the invalid value
  2. 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 helper createRoute 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

📥 Commits

Reviewing files that changed from the base of the PR and between 58c48c1 and 48884b7.

📒 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:

  1. Explicit soft delete
  2. Default to soft delete when no type is specified
  3. 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:

  1. Error scenarios (e.g., restoring an active subscription)
  2. Invalid state transitions
  3. 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.
Copy link

@coderabbitai coderabbitai bot left a 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 suggestion

Implement 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:

  1. Mark all storage topics for deletion
  2. Execute deletion with rollback capability
server/src/main/java/com/flipkart/varadhi/services/SubscriptionService.java (1)

338-355: ⚠️ Potential issue

Implement 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:

  1. Edge cases:

    • Topics with special characters in names
    • Maximum length names
    • Empty or null project names
  2. Error conditions:

    • Invalid action codes
    • Null capacity policies
    • Invalid conversions
  3. 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:

  1. The region should be derived from TopicResource
  2. 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:

  1. Empty string deletion type
  2. 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:

  1. Makes tests brittle and harder to maintain
  2. 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:

  1. Adding null checks for parameters in markActive and markInactive.
  2. 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:

  1. Invalid state transitions (e.g., attempting to activate an already active topic)
  2. Invalid action codes
  3. 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 nested Action 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 to RESTORE or MIGRATE 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
Fields capacity (line 26) and actionCode (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
The getMultiShard and getShardCapacity 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 simple 1/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 implements Validatable, 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

📥 Commits

Reviewing files that changed from the base of the PR and between 48884b7 and 514abe4.

📒 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 to DEFAULT_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:

  1. Restoring a non-existent topic
  2. Restoring a topic that wasn't soft-deleted
  3. 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() to builder() 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:

  1. Topic creation with different lifecycle states
  2. Validation of soft-deleted topics
  3. 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 than fail() for handling unsupported HTTP methods.


113-182: LGTM! Improved method naming and response handling.

The changes enhance code clarity through:

  1. More descriptive method names (e.g., sendRequestWithBodysendRequestWithEntity)
  2. Robust null/empty response handling
  3. 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-case defaultTopicCapacity entries remain only as configuration keys and variable names, which is expected.

  • PulsarTopicServiceTest.java and several other test files correctly use Constants.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 but metaStore.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 of ResourceType.ROOT for listing organizations
Using ResourceType.ROOT for the ORG_LIST action (line 20) might be counterintuitive if the rest of the ORG actions use ResourceType.ORG. Verify that the logic for listing organizations through the root resource is intentional and consistent with other resource types.


44-52: Validate the new RESTORE, CONSUME, and PRODUCE 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-null actionCode at construction
The constructor (lines 33–55) introduces actionCode for each TopicResource. 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
The from(VaradhiTopic) method (lines 88–104) and toVaradhiTopic() method (lines 108–114) correctly pass actionCode. 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 that actionCode is preserved through these conversions.
[approve]

server/src/main/java/com/flipkart/varadhi/utils/VaradhiSubscriptionFactory.java (2)

Line range hint 65-92: Confirm usage of subscriptionResource.getActionCode()
The get method (lines 65–92) now includes the subscription’s actionCode. 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 for getSubscriptionShards
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 of assertTrue(!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:

  1. Concurrent lifecycle operations
  2. Race conditions in state transitions
  3. 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:

  1. Endpoint configuration
  2. Retry policy
  3. Consumption policy
  4. Version
  5. Status

The past review comments already identified this gap. The suggested implementation in the past review would improve the test's reliability.

Comment on lines +37 to +59
@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())
);
}
Copy link

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 to SubscriptionResource
  • Different lifecycle status states and action codes
  • Restoration scenarios

This will ensure the new soft-deletion functionality is properly tested.

Comment on lines +15 to +74
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());
}
}
Copy link

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 transitions
  • testHardDeletionState: Verify the hard-deletion state transitions
  • testRestorationFlow: Verify the restoration process
  • testInvalidStateTransitions: 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;
Copy link

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 uses Constants.DEFAULT_TOPIC_CAPACITY.
  • However, the search revealed that server/src/main/java/com/flipkart/varadhi/verticles/webserver/WebServerVerticle.java still references the old constant DefaultTopicCapacity. 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

Comment on lines +49 to +63
@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());
}
Copy link

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.

Comment on lines +65 to +110
@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);
}
Copy link

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:

  1. Edge cases (empty strings, null values, negative numbers)
  2. Error scenarios
  3. 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.

Suggested change
@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);
}

Comment on lines +162 to +166
// 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();
Copy link

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

Comment on lines 211 to +237
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);
}
Copy link

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:

  1. Topic existence/state
  2. 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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: No status
Development

Successfully merging this pull request may close these issues.

Add soft-deletion support for Varadhi Resources
1 participant