Repository: continuousactivelearning/vibe
Pull request #273: feat(courseVersion): Cascade Delete course version + Deletion Test Scripts.
Author: login: insaneonai
Labels: name: backend
Created: 2025-04-17T11:43:48Z | Merged: 2025-04-22T06:21:08Z

Implemented courseVersion deletion endpoint `/courses/:courseId/versions/:versionId`.

What it does:

1.  Deletes the course Version (including sections and modules associated with the version).
2.  Deletes All ItemGroups associated with the courseVersion (Cascade).
3.  Removes the versionId in the corresponding course entity (thus updating the timestamp of course document after update).

**Please let me know if i missed out any, open to changes.**

Closes #272 

Linked issue #272: feat(courses): implement delete courseVersion endpoint
- Implement an endpoint to delete a `courseVersion` by its ID.
- When a courseVersion is deleted, also cascade delete:
  - All `modules` in that version.
  - All `sections` in each module.
  - All `items` through `itemGroup` in those sections.
- Make sure timestamps and parent-child relationships are handled cleanly.
- Update documentation to reflect the cascade behavior.
- Write integration tests for full deletion cascade logic.

- [x] Endpoint
- [ ] Tests for Endpoint
Linked issue comments: user: login: insaneonai
created_at: 2025-04-17T12:06:29Z
body: @adityabmv can you check the below PR #273
author_association: CONTRIBUTOR

Diff:
diff --git a/backend/src/modules/courses/classes/validators/CourseVersionValidators.ts b/backend/src/modules/courses/classes/validators/CourseVersionValidators.ts
index cde9691a1..c475d9966 100644
--- a/backend/src/modules/courses/classes/validators/CourseVersionValidators.ts
+++ b/backend/src/modules/courses/classes/validators/CourseVersionValidators.ts
@@ -58,8 +58,28 @@ class ReadCourseVersionParams {
   id: string;
 }
 
+/**
+ * Route parameters for deleting a course version by ID.
+ *
+ * @category Courses/Validators/CourseVersionValidators
+ */
+
+class DeleteCourseVersionParams {
+  /**
+   * ID of the course version to delete.
+   */
+  @IsMongoId()
+  @IsString()
+  versionId: string;
+
+  @IsMongoId()
+  @IsString()
+  courseId: string;
+}
+
 export {
   CreateCourseVersionBody,
   CreateCourseVersionParams,
   ReadCourseVersionParams,
+  DeleteCourseVersionParams,
 };
diff --git a/backend/src/modules/courses/controllers/CourseVersionController.ts b/backend/src/modules/courses/controllers/CourseVersionController.ts
index cfe5ca2e2..64f7b39bc 100644
--- a/backend/src/modules/courses/controllers/CourseVersionController.ts
+++ b/backend/src/modules/courses/controllers/CourseVersionController.ts
@@ -9,15 +9,18 @@ import {
   JsonController,
   Params,
   Post,
+  Delete,
+  BadRequestError,
 } from 'routing-controllers';
 import {CourseRepository} from 'shared/database/providers/mongo/repositories/CourseRepository';
-import {ItemNotFoundError, ReadError} from 'shared/errors/errors';
+import {DeleteError, ItemNotFoundError, ReadError} from 'shared/errors/errors';
 import {Inject, Service} from 'typedi';
 import {CourseVersion} from '../classes/transformers/CourseVersion';
 import {
   CreateCourseVersionParams,
   CreateCourseVersionBody,
   ReadCourseVersionParams,
+  DeleteCourseVersionParams,
 } from '../classes/validators/CourseVersionValidators';
 
 /**
@@ -116,4 +119,37 @@ export class CourseVersionController {
       throw new HttpError(500, error.message);
     }
   }
+  /**
+   * Delete the course version by its ID
+   *
+   * @params params - Parameters including the courseID and version ID.
+   * @returns The deleted course version object.
+   *
+   * @throws HttpError(404) if the course or version is not found.
+   * @throws HttpError(500) on any other internal server errors.
+   *
+   * @category Courses/Controllers
+   */
+
+  @Authorized(['admin', 'instructor'])
+  @Delete('/:courseId/versions/:versionId')
+  async delete(@Params() params: DeleteCourseVersionParams) {
+    const {courseId, versionId} = params;
+    if (!versionId || !courseId) {
+      throw new BadRequestError('Version ID is required');
+    }
+    try {
+      const version = await this.courseRepo.deleteVersion(courseId, versionId);
+      return {
+        message: `Version with the ID ${versionId} has been deleted successfully.`,
+      };
+    } catch (error) {
+      if (error instanceof ItemNotFoundError) {
+        throw new HttpError(404, error.message);
+      }
+      if (error instanceof DeleteError) {
+        throw new HttpError(500, error.message);
+      }
+    }
+  }
 }
diff --git a/backend/src/modules/courses/tests/CourseVersionController.test.ts b/backend/src/modules/courses/tests/CourseVersionController.test.ts
index ea2a2f0cb..78443a8b5 100644
--- a/backend/src/modules/courses/tests/CourseVersionController.test.ts
+++ b/backend/src/modules/courses/tests/CourseVersionController.test.ts
@@ -274,4 +274,100 @@ describe('Course Version Controller Integration Tests', () => {
       });
     });
   });
+
+  // Delete course version
+  describe('COURSE VERSION DELETE', () => {
+    const coursePayload = {
+      name: 'New Course',
+      description: 'Course description',
+    };
+
+    const courseVersionPayload = {
+      version: 'New Course Version',
+      description: 'Course version description',
+    };
+
+    const modulePayload = {
+      name: 'New Module',
+      description: 'Module description',
+    };
+
+    const sectionPayload = {
+      name: 'New Section',
+      description: 'Section description',
+    };
+
+    const itemPayload = {
+      name: 'Item1',
+      description: 'This an item',
+      type: 'VIDEO',
+      videoDetails: {
+        URL: 'http://url.com',
+        startTime: '00:00:00',
+        endTime: '00:00:40',
+        points: '10.5',
+      },
+    };
+
+    describe('Success Scenario', () => {
+      it('should delete a course version', async () => {
+        const courseResponse = await request(app)
+          .post('/courses/')
+          .send(coursePayload)
+          .expect(200);
+
+        const courseId = courseResponse.body._id;
+
+        const versionResponse = await request(app)
+          .post(`/courses/${courseId}/versions`)
+          .send(courseVersionPayload)
+          .expect(200);
+
+        const versionId = versionResponse.body.version._id;
+
+        const moduleResponse = await request(app)
+          .post(`/courses/versions/${versionId}/modules`)
+          .send(modulePayload)
+          .expect(200);
+
+        const moduleId = moduleResponse.body.version.modules[0].moduleId;
+
+        const sectionResponse = await request(app)
+          .post(`/versions/${versionId}/modules/${moduleId}/sections`)
+          .send(sectionPayload)
+          .expect(200);
+
+        const sectionId =
+          sectionResponse.body.version.modules[0].sections[0].sectionId;
+
+        const itemsGroupId =
+          sectionResponse.body.version.modules[0].sections[0].itemsGroupId;
+
+        const itemsGroupResponse = await request(app)
+          .post(
+            `/versions/${versionId}/modules/${moduleId}/sections/${sectionId}/items`,
+          )
+          .send(itemPayload)
+          .expect(200);
+
+        const deleteVersion = await request(app)
+          .delete(`/courses/${courseId}/versions/${versionId}`)
+          .expect(200);
+        expect(deleteVersion.body.deletedItem);
+      });
+    });
+    describe('Failure Scenario', () => {
+      it('should not delete a course version', async () => {
+        // invalid MongoId
+        await request(app).delete('/courses/123/versions/123').expect(400);
+
+        // course version or course id not found.
+        await request(app)
+          .delete(
+            '/courses/5f9b1b3c9d1f1f1f1f1f1f1f/versions/5f9b1b3c9d1f1f1f1f1f1f1f',
+          )
+          .expect(404);
+      });
+    });
+  });
 });
diff --git a/backend/src/modules/courses/tests/ItemsController.test.ts b/backend/src/modules/courses/tests/ItemsController.test.ts
index f425e8052..fb1ed3610 100644
--- a/backend/src/modules/courses/tests/ItemsController.test.ts
+++ b/backend/src/modules/courses/tests/ItemsController.test.ts
@@ -1,12 +1,11 @@
-import {coursesModuleOptions, ItemsGroup} from 'modules/courses';
+import {coursesModuleOptions} from 'modules/courses';
 import {MongoMemoryServer} from 'mongodb-memory-server';
-import {RoutingControllersOptions, useExpressServer} from 'routing-controllers';
+import {useExpressServer} from 'routing-controllers';
 import {CourseRepository} from 'shared/database/providers/mongo/repositories/CourseRepository';
 import {MongoDatabase} from 'shared/database/providers/MongoDatabaseProvider';
 import Container from 'typedi';
 import Express from 'express';
 import request from 'supertest';
-import {ReadError} from 'shared/errors/errors';
 
 describe('Item Controller Integration Tests', () => {
   const App = Express();
diff --git a/backend/src/shared/database/providers/mongo/repositories/CourseRepository.ts b/backend/src/shared/database/providers/mongo/repositories/CourseRepository.ts
index 781739fef..504778be4 100644
--- a/backend/src/shared/database/providers/mongo/repositories/CourseRepository.ts
+++ b/backend/src/shared/database/providers/mongo/repositories/CourseRepository.ts
@@ -177,12 +177,83 @@ export class CourseRepository implements ICourseRepository {
       );
     }
   }
+
+  async deleteVersion(
+    courseId: string,
+    versionId: string,
+  ): Promise<CourseVersion | null> {
+    await this.init();
+    try {
+      // 1. find the course version to Delete.
+      const courseVersion = await this.courseVersionCollection.findOne({
+        _id: new ObjectId(versionId),
+      });
+
+      const course = await this.courseCollection.findOne({
+        _id: new ObjectId(courseId),
+      });
+
+      if (!course) {
+        throw new ItemNotFoundError('Course not found');
+      }
+
+      // 2. check if the course version exists.
+      if (!courseVersion) {
+        throw new ItemNotFoundError('Course Version not found');
+      }
+
+      // 3. Extract itemGroupsIds before deleting the course version.
+      const itemGroupsIds = courseVersion.modules.flatMap(module =>
+        module.sections.map(section => new ObjectId(section.itemsGroupId)),
+      );
+
+      // 4. Delete course version
+      const versionDeleteResult = await this.courseVersionCollection.deleteOne({
+        _id: new ObjectId(versionId),
+      });
+
+      if (versionDeleteResult.deletedCount !== 1) {
+        throw new DeleteError('Failed to delete course version');
+      }
+
+      // 5. Remove courseVersionId from the course
+
+      const courseUpdateResult = await this.courseCollection.updateOne(
+        {_id: new ObjectId(courseId)},
+        {$pull: {versions: versionId}},
+      );
+
+      if (courseUpdateResult.modifiedCount !== 1) {
+        throw new DeleteError('Failed to update course');
+      }
+
+      // 6. Cascade Delete item groups
+
+      const itemDeletionResult = await this.itemsGroupCollection.deleteMany({
+        _id: {$in: itemGroupsIds},
+      });
+
+      if (itemDeletionResult.deletedCount === 0) {
+        throw new DeleteError('Failed to delete item groups');
+      }
+
+      // 7. Return the deleted course version
+      return courseVersion;
+    } catch (error) {
+      if (error instanceof ItemNotFoundError) {
+        throw error;
+      }
+      throw new DeleteError(
+        'Failed to delete course version.\n More Details: ' + error,
+      );
+    }
+  }
+
   async createItemsGroup(itemsGroup: ItemsGroup): Promise<ItemsGroup | null> {
     await this.init();
     try {
       const result = await this.itemsGroupCollection.insertOne(itemsGroup);
       if (result) {
-        console.log('Items created', result.insertedId);
         const newItems = await this.itemsGroupCollection.findOne({
           _id: result.insertedId,
         });


Reviews:
user: login: copilot-pull-request-reviewer[bot]
body: ## Pull Request Overview

This PR implements a deletion endpoint for course versions which cascades deletion to associated modules, sections, and item groups while updating the course entity. Key changes include:
- Adding the deleteVersion method to the CourseRepository for handling cascade deletion.
- Implementing a DELETE endpoint in CourseVersionController with proper error handling.
- Introducing integration tests for both success and failure deletion scenarios, and adding a validator for course version deletion.

### Reviewed Changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

<details>
<summary>Show a summary per file</summary>

| File | Description |
| ---- | ----------- |
| backend/src/shared/database/providers/mongo/repositories/CourseRepository.ts | Adds the deleteVersion method with cascade deletion for course versions and associated item groups. |
| backend/src/modules/courses/tests/ItemsController.test.ts | Updates test imports by removing unused imports. |
| backend/src/modules/courses/tests/CourseVersionController.test.ts | Adds integration tests for the course version deletion endpoint. |
| backend/src/modules/courses/controllers/CourseVersionController.ts | Implements the DELETE route using the deleteVersion method. |
| backend/src/modules/courses/classes/validators/CourseVersionValidators.ts | Adds DeleteCourseVersionParams to validate deletion parameters. |
</details>





state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-21T05:25:52Z
user: login: adityabmv
state: CHANGES_REQUESTED
author_association: COLLABORATOR
submitted_at: 2025-04-21T07:24:16Z
user: login: adityabmv
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-21T07:25:22Z

Review comments:
diff_hunk: @@ -177,6 +177,75 @@ export class CourseRepository implements ICourseRepository {
       );
     }
   }
+
+  async deleteVersion(
+    courseId: string,
+    versionId: string,
+  ): Promise<CourseVersion | null> {
+    await this.init();
+    try {
+      // 1. find the course version to Delete.
+      const courseVersion = await this.courseVersionCollection.findOne({
+        _id: new ObjectId(versionId),
+      });
+
+      const course = await this.courseCollection.findOne({
+        _id: new ObjectId(courseId),
+      });
+
+      if (!course) {
+        throw new ItemNotFoundError('Course not found');
+      }
+
+      // 2. check if the course version exists.
+      if (!courseVersion) {
+        throw new ItemNotFoundError('Course Version not found');
+      }
+
+      // 3. Extract itemGroupsIds before deleting the course version.
+      const itemGroupsIds = courseVersion.modules.flatMap(module =>
+        module.sections.map(section => new ObjectId(section.itemsGroupId)),
+      );
+
+      // 4. Delete course version
+      const versionDeleteResult = await this.courseVersionCollection.deleteOne({
+        _id: new ObjectId(versionId),
+      });
+
+      if (versionDeleteResult.deletedCount !== 1) {
+        throw new DeleteError('Failed to delete course version');
+      }
+
+      // 5. Remove courseVersionId from the course
+
+      const courseUpdateResult = await this.courseCollection.updateOne(
+        {_id: new ObjectId(courseId)},
+        {$pull: {versions: versionId}},
+      );
+
+      if (courseUpdateResult.modifiedCount !== 1) {
+        throw new DeleteError('Failed to update course');
+      }
+
+      // 6. Cascade Delete item groups
+
+      const itemDeletionResult = await this.itemsGroupCollection.deleteMany({
+        _id: {$in: itemGroupsIds.map(id => new ObjectId(id))},
path: backend/src/shared/database/providers/mongo/repositories/CourseRepository.ts
user: login: Copilot
body: The itemGroupsIds are already converted to ObjectId in line 207, so mapping them to a new ObjectId again in the deleteMany query is redundant and could lead to unexpected behavior. Remove the extra conversion.
```suggestion
        _id: {$in: itemGroupsIds},
```
created_at: 2025-04-21T05:25:52Z
author_association: CONTRIBUTOR
diff_hunk: @@ -58,8 +58,28 @@ class ReadCourseVersionParams {
   id: string;
 }
 
+/**
+ * Route parameters for deleting a course version by ID.
+ *
+ * @category Courses/Validators/CourseVersionValidators
+ */
+
+class DeleteCourseVersionParams {
+  /**
+   * ID of the course version to delete.
+   */
+  @IsMongoId()
+  @IsString()
+  id: string;
+
+  @IsMongoId()
+  @IsString()
+  cid: string;
path: backend/src/modules/courses/classes/validators/CourseVersionValidators.ts
user: login: adityabmv
body: Rename ambiguous id and cid fields to descriptive, context‑specific names for improved clarity.
created_at: 2025-04-21T07:02:30Z
author_association: COLLABORATOR
diff_hunk: @@ -116,4 +119,38 @@ export class CourseVersionController {
       throw new HttpError(500, error.message);
     }
   }
+  /**
+   * Delete the course version by its ID
+   *
+   * @params params - Parameters including the courseID and version ID.
+   * @returns The deleted course version object.
+   *
+   * @throws HttpError(404) if the course or version is not found.
+   * @throws HttpError(500) on any other internal server errors.
+   *
+   * @category Courses/Controllers
+   */
+
+  @Authorized(['admin', 'instructor'])
+  @Delete('/:cid/versions/:id')
path: backend/src/modules/courses/controllers/CourseVersionController.ts
user: login: adityabmv
body: Please use the parameters named courseId and versionId.

created_at: 2025-04-21T07:04:10Z
author_association: COLLABORATOR
diff_hunk: @@ -116,4 +119,38 @@ export class CourseVersionController {
       throw new HttpError(500, error.message);
     }
   }
+  /**
+   * Delete the course version by its ID
+   *
+   * @params params - Parameters including the courseID and version ID.
+   * @returns The deleted course version object.
+   *
+   * @throws HttpError(404) if the course or version is not found.
+   * @throws HttpError(500) on any other internal server errors.
+   *
+   * @category Courses/Controllers
+   */
+
+  @Authorized(['admin', 'instructor'])
+  @Delete('/:cid/versions/:id')
+  async delete(@Params() params: DeleteCourseVersionParams) {
+    const {cid, id} = params;
+    if (!id || !cid) {
+      throw new BadRequestError('Version ID is required');
+    }
+    try {
+      const version = await this.courseRepo.deleteVersion(cid, id);
+      version._id = version._id.toString();
+      return {
+        deletedItem: instanceToPlain(version),
path: backend/src/modules/courses/controllers/CourseVersionController.ts
user: login: adityabmv
body: You could return an object containing a confirmation message such as:
“Version with the ID {id} has been deleted successfully.”
created_at: 2025-04-21T07:08:50Z
author_association: COLLABORATOR
diff_hunk: @@ -116,4 +119,38 @@ export class CourseVersionController {
       throw new HttpError(500, error.message);
     }
   }
+  /**
+   * Delete the course version by its ID
+   *
+   * @params params - Parameters including the courseID and version ID.
+   * @returns The deleted course version object.
+   *
+   * @throws HttpError(404) if the course or version is not found.
+   * @throws HttpError(500) on any other internal server errors.
+   *
+   * @category Courses/Controllers
+   */
+
+  @Authorized(['admin', 'instructor'])
+  @Delete('/:cid/versions/:id')
+  async delete(@Params() params: DeleteCourseVersionParams) {
+    const {cid, id} = params;
+    if (!id || !cid) {
+      throw new BadRequestError('Version ID is required');
+    }
+    try {
+      const version = await this.courseRepo.deleteVersion(cid, id);
+      version._id = version._id.toString();
+      return {
+        deletedItem: instanceToPlain(version),
+      };
+    } catch (error) {
+      if (error instanceof BadRequestError) {
path: backend/src/modules/courses/controllers/CourseVersionController.ts
user: login: adityabmv
body: BadRequestError will never be thrown by any of the code in try block.
Handle ItemNotFoundError.

created_at: 2025-04-21T07:11:56Z
author_association: COLLABORATOR
diff_hunk: @@ -177,6 +177,75 @@ export class CourseRepository implements ICourseRepository {
       );
     }
   }
+
+  async deleteVersion(
+    courseId: string,
+    versionId: string,
+  ): Promise<CourseVersion | null> {
+    await this.init();
+    try {
+      // 1. find the course version to Delete.
+      const courseVersion = await this.courseVersionCollection.findOne({
+        _id: new ObjectId(versionId),
+      });
+
+      const course = await this.courseCollection.findOne({
+        _id: new ObjectId(courseId),
+      });
+
+      if (!course) {
+        throw new ItemNotFoundError('Course not found');
+      }
+
+      // 2. check if the course version exists.
+      if (!courseVersion) {
+        throw new ItemNotFoundError('Course Version not found');
+      }
+
+      // 3. Extract itemGroupsIds before deleting the course version.
+      const itemGroupsIds = courseVersion.modules.flatMap(module =>
+        module.sections.map(section => new ObjectId(section.itemsGroupId)),
+      );
+
+      // 4. Delete course version
+      const versionDeleteResult = await this.courseVersionCollection.deleteOne({
+        _id: new ObjectId(versionId),
+      });
+
+      if (versionDeleteResult.deletedCount !== 1) {
+        throw new DeleteError('Failed to delete course version');
+      }
+
+      // 5. Remove courseVersionId from the course
+
+      const courseUpdateResult = await this.courseCollection.updateOne(
+        {_id: new ObjectId(courseId)},
+        {$pull: {versions: versionId}},
+      );
+
+      if (courseUpdateResult.modifiedCount !== 1) {
+        throw new DeleteError('Failed to update course');
+      }
+
+      // 6. Cascade Delete item groups
+
+      const itemDeletionResult = await this.itemsGroupCollection.deleteMany({
+        _id: {$in: itemGroupsIds.map(id => new ObjectId(id))},
+      });
+
+      if (itemDeletionResult.deletedCount === 0) {
+        throw new DeleteError('Failed to delete item groups');
+      }
+
+      // 7. Return the deleted course version
+      return courseVersion;
+    } catch (error) {
+      throw new Error(
path: backend/src/shared/database/providers/mongo/repositories/CourseRepository.ts
user: login: adityabmv
body: You could throw a `DeleteError`, instead of `Error`
created_at: 2025-04-21T07:13:23Z
author_association: COLLABORATOR
diff_hunk: @@ -177,6 +177,75 @@ export class CourseRepository implements ICourseRepository {
       );
     }
   }
+
+  async deleteVersion(
+    courseId: string,
+    versionId: string,
+  ): Promise<CourseVersion | null> {
+    await this.init();
+    try {
+      // 1. find the course version to Delete.
+      const courseVersion = await this.courseVersionCollection.findOne({
+        _id: new ObjectId(versionId),
+      });
+
+      const course = await this.courseCollection.findOne({
+        _id: new ObjectId(courseId),
+      });
+
+      if (!course) {
+        throw new ItemNotFoundError('Course not found');
+      }
+
+      // 2. check if the course version exists.
+      if (!courseVersion) {
+        throw new ItemNotFoundError('Course Version not found');
+      }
+
+      // 3. Extract itemGroupsIds before deleting the course version.
+      const itemGroupsIds = courseVersion.modules.flatMap(module =>
+        module.sections.map(section => new ObjectId(section.itemsGroupId)),
+      );
+
+      // 4. Delete course version
+      const versionDeleteResult = await this.courseVersionCollection.deleteOne({
+        _id: new ObjectId(versionId),
+      });
+
+      if (versionDeleteResult.deletedCount !== 1) {
+        throw new DeleteError('Failed to delete course version');
+      }
+
+      // 5. Remove courseVersionId from the course
+
+      const courseUpdateResult = await this.courseCollection.updateOne(
+        {_id: new ObjectId(courseId)},
+        {$pull: {versions: versionId}},
+      );
+
+      if (courseUpdateResult.modifiedCount !== 1) {
+        throw new DeleteError('Failed to update course');
+      }
+
+      // 6. Cascade Delete item groups
+
+      const itemDeletionResult = await this.itemsGroupCollection.deleteMany({
+        _id: {$in: itemGroupsIds.map(id => new ObjectId(id))},
path: backend/src/shared/database/providers/mongo/repositories/CourseRepository.ts
user: login: adityabmv
body: Please also resolve this @insaneonai 
created_at: 2025-04-21T07:25:22Z
author_association: COLLABORATOR

PR comments:
user: login: insaneonai
created_at: 2025-04-18T11:28:50Z
body: Hi, @adityabmv can you please check this out?
author_association: CONTRIBUTOR
user: login: adityabmv
created_at: 2025-04-18T11:32:14Z
body: Hi @insaneonai thanks for your contribution!

I’ll review this PR and get back to you by Monday or Tuesday.
In the meantime, feel free to pick up any other open issues you’d like to work on.
author_association: COLLABORATOR
user: login: insaneonai
created_at: 2025-04-18T11:35:06Z
body: Ok, thank you @adityabmv i will continue my works on #243 .
author_association: CONTRIBUTOR
user: login: insaneonai
created_at: 2025-04-21T13:04:45Z
body: @adityabmv I have resolved the suggested changes. please let me know it everything looks good.
author_association: CONTRIBUTOR
user: login: adityabmv
created_at: 2025-04-22T06:16:52Z
body: @insaneonai , thank you for your contribution!

The test cases provided are a good start, but they are not extensive enough. I will proceed with merging this PR for now. However, it would be great if you could increase the test coverage by adding more test cases. Once you've done that, please feel free to submit another PR.

Thanks again for your work!
author_association: COLLABORATOR