From 81c88b5ba2f76a3a82889fa4fa23b9447c12a891 Mon Sep 17 00:00:00 2001 From: christopherjohnson Date: Tue, 3 Feb 2026 08:35:13 +0000 Subject: [PATCH 1/5] allow deletetion by lti context id --- .../edu/ksu/canvas/impl/AssignmentImpl.java | 32 +++++++++++++++++++ .../canvas/interfaces/AssignmentWriter.java | 9 ++++++ 2 files changed, 41 insertions(+) diff --git a/src/main/java/edu/ksu/canvas/impl/AssignmentImpl.java b/src/main/java/edu/ksu/canvas/impl/AssignmentImpl.java index df132f4e..aeebf19e 100644 --- a/src/main/java/edu/ksu/canvas/impl/AssignmentImpl.java +++ b/src/main/java/edu/ksu/canvas/impl/AssignmentImpl.java @@ -11,6 +11,7 @@ import edu.ksu.canvas.requestOptions.GetSingleAssignmentOptions; import edu.ksu.canvas.requestOptions.ListCourseAssignmentsOptions; import edu.ksu.canvas.requestOptions.ListUserAssignmentOptions; +import edu.ksu.canvas.exception.CanvasException; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; @@ -75,6 +76,37 @@ public Optional deleteAssignment(String courseId, Integer assignment return responseParser.parseToObject(Assignment.class, response); } + @Override + public Optional deleteAssignment(String courseId, String assignmentId) throws IOException { + Map> postParams = new HashMap<>(); + postParams.put("event", Collections.singletonList("delete")); + String url = buildCanvasUrl("courses/" + courseId + "/assignments/" + assignmentId, Collections.emptyMap()); + Response response = canvasMessenger.deleteFromCanvas(oauthToken, url, postParams); + LOG.debug("response {}", response); + int code = response.getResponseCode(); + + boolean errorHappened = response.getErrorHappened(); + + if (!errorHappened) { + switch (code) { + case 200: + return responseParser.parseToObject(Assignment.class, response); + case 204: + case 404: // already deleted, treat as success + return Optional.empty(); + default: + // fall through to error + } + } + + String msg = response.getContent(); + if (msg == null || msg.isBlank()) { + msg = "Canvas returned HTTP " + code; + } + + throw new CanvasException(msg, url, response); + } + @Override public Optional editAssignment(String courseId, Assignment assignment) throws IOException { String url = buildCanvasUrl("courses/" + courseId + "/assignments/" + assignment.getId(), Collections.emptyMap()); diff --git a/src/main/java/edu/ksu/canvas/interfaces/AssignmentWriter.java b/src/main/java/edu/ksu/canvas/interfaces/AssignmentWriter.java index a1e0546c..ff884a0a 100644 --- a/src/main/java/edu/ksu/canvas/interfaces/AssignmentWriter.java +++ b/src/main/java/edu/ksu/canvas/interfaces/AssignmentWriter.java @@ -25,6 +25,15 @@ public interface AssignmentWriter extends CanvasWriter deleteAssignment(String courseId, Integer assignmentId) throws IOException; + /** + * Deletes a specified assignment in canvas. Override allows us to pass assignment IDs that are non-numeric (e.g. "lti_context_id:ab84f579-4442-4d4a-acd8-85c5ec6fd2b6"). + * @param courseId Course ID of course to delete assignment from + * @param assignmentId Assignment ID of assignment to delete + * @return The deleted Assignment object as returned by the Canvas API + * @throws IOException When there is an error communicating with Canvas + */ + Optional deleteAssignment(String courseId, String assignmentId) throws IOException; + /** * Writes an Assignment object to the Canvas API * @param courseId Course ID that this assignment is associated with From db88c15d62be4f0d729f9e0d642ec57a22cfe9ed Mon Sep 17 00:00:00 2001 From: sebastianchristopher <40264653+sebastianchristopher@users.noreply.github.com> Date: Tue, 3 Feb 2026 08:44:33 +0000 Subject: [PATCH 2/5] Update src/main/java/edu/ksu/canvas/interfaces/AssignmentWriter.java Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/main/java/edu/ksu/canvas/interfaces/AssignmentWriter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/edu/ksu/canvas/interfaces/AssignmentWriter.java b/src/main/java/edu/ksu/canvas/interfaces/AssignmentWriter.java index ff884a0a..a52816c0 100644 --- a/src/main/java/edu/ksu/canvas/interfaces/AssignmentWriter.java +++ b/src/main/java/edu/ksu/canvas/interfaces/AssignmentWriter.java @@ -26,7 +26,7 @@ public interface AssignmentWriter extends CanvasWriter deleteAssignment(String courseId, Integer assignmentId) throws IOException; /** - * Deletes a specified assignment in canvas. Override allows us to pass assignment IDs that are non-numeric (e.g. "lti_context_id:ab84f579-4442-4d4a-acd8-85c5ec6fd2b6"). + * Deletes a specified assignment in Canvas. This overload allows us to pass assignment IDs that are non-numeric (e.g. "lti_context_id:ab84f579-4442-4d4a-acd8-85c5ec6fd2b6"). * @param courseId Course ID of course to delete assignment from * @param assignmentId Assignment ID of assignment to delete * @return The deleted Assignment object as returned by the Canvas API From 046f4e1f148a1b36339927a5e1a37ecc785ab4f3 Mon Sep 17 00:00:00 2001 From: christopherjohnson Date: Wed, 4 Feb 2026 09:18:00 +0000 Subject: [PATCH 3/5] allow deletion by lti context id --- .../edu/ksu/canvas/impl/AssignmentImpl.java | 44 +++++-------------- 1 file changed, 11 insertions(+), 33 deletions(-) diff --git a/src/main/java/edu/ksu/canvas/impl/AssignmentImpl.java b/src/main/java/edu/ksu/canvas/impl/AssignmentImpl.java index aeebf19e..29ef2278 100644 --- a/src/main/java/edu/ksu/canvas/impl/AssignmentImpl.java +++ b/src/main/java/edu/ksu/canvas/impl/AssignmentImpl.java @@ -11,7 +11,6 @@ import edu.ksu.canvas.requestOptions.GetSingleAssignmentOptions; import edu.ksu.canvas.requestOptions.ListCourseAssignmentsOptions; import edu.ksu.canvas.requestOptions.ListUserAssignmentOptions; -import edu.ksu.canvas.exception.CanvasException; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; @@ -64,47 +63,26 @@ public Optional createAssignment(String courseId, Assignment assignm @Override public Optional deleteAssignment(String courseId, Integer assignmentId) throws IOException { - Map> postParams = new HashMap<>(); - postParams.put("event", Collections.singletonList("delete")); - String createdUrl = buildCanvasUrl("courses/" + courseId + "/assignments/" + assignmentId, Collections.emptyMap()); - Response response = canvasMessenger.deleteFromCanvas(oauthToken, createdUrl, postParams); - LOG.debug("response " + response.toString()); - if(response.getErrorHappened() || response.getResponseCode() != 200){ - LOG.debug("Failed to delete assignment, error message: " + response.toString()); - return Optional.empty(); - } - return responseParser.parseToObject(Assignment.class, response); + String url = buildCanvasUrl("courses/" + courseId + "/assignments/" + assignmentId, Collections.emptyMap()); + return deleteAssignment(url); } @Override public Optional deleteAssignment(String courseId, String assignmentId) throws IOException { + String url = buildCanvasUrl("courses/" + courseId + "/assignments/" + assignmentId, Collections.emptyMap()); + return deleteAssignment(url); + } + + private Optional deleteAssignment(String url) throws IOException { Map> postParams = new HashMap<>(); postParams.put("event", Collections.singletonList("delete")); - String url = buildCanvasUrl("courses/" + courseId + "/assignments/" + assignmentId, Collections.emptyMap()); Response response = canvasMessenger.deleteFromCanvas(oauthToken, url, postParams); LOG.debug("response {}", response); - int code = response.getResponseCode(); - - boolean errorHappened = response.getErrorHappened(); - - if (!errorHappened) { - switch (code) { - case 200: - return responseParser.parseToObject(Assignment.class, response); - case 204: - case 404: // already deleted, treat as success - return Optional.empty(); - default: - // fall through to error - } - } - - String msg = response.getContent(); - if (msg == null || msg.isBlank()) { - msg = "Canvas returned HTTP " + code; + if(response.getErrorHappened() || response.getResponseCode() != 200){ + LOG.debug("Failed to delete assignment, error message: {}", response); + return Optional.empty(); } - - throw new CanvasException(msg, url, response); + return responseParser.parseToObject(Assignment.class, response); } @Override From b72f923ca97d7db715de98a3ab1f9b2b80944243 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 4 Feb 2026 09:34:31 +0000 Subject: [PATCH 4/5] Initial plan From 93cdc56691fa3ee79354bdd272aaf1c20dd159bd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 4 Feb 2026 09:36:58 +0000 Subject: [PATCH 5/5] Add test coverage for deleteAssignment methods Co-authored-by: sebastianchristopher <40264653+sebastianchristopher@users.noreply.github.com> --- .../assignment/AssignmentWriterUTest.java | 63 +++++++++++++++++++ .../assignment/DeletedAssignment.json | 40 ++++++++++++ 2 files changed, 103 insertions(+) create mode 100644 src/test/java/edu/ksu/canvas/tests/assignment/AssignmentWriterUTest.java create mode 100644 src/test/resources/SampleJson/assignment/DeletedAssignment.json diff --git a/src/test/java/edu/ksu/canvas/tests/assignment/AssignmentWriterUTest.java b/src/test/java/edu/ksu/canvas/tests/assignment/AssignmentWriterUTest.java new file mode 100644 index 00000000..99cedb0c --- /dev/null +++ b/src/test/java/edu/ksu/canvas/tests/assignment/AssignmentWriterUTest.java @@ -0,0 +1,63 @@ +package edu.ksu.canvas.tests.assignment; + +import edu.ksu.canvas.CanvasTestBase; +import edu.ksu.canvas.impl.AssignmentImpl; +import edu.ksu.canvas.interfaces.AssignmentWriter; +import edu.ksu.canvas.model.assignment.Assignment; +import edu.ksu.canvas.net.FakeRestClient; +import junit.framework.AssertionFailedError; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import java.io.IOException; +import java.util.Optional; + +public class AssignmentWriterUTest extends CanvasTestBase { + + @Autowired + private FakeRestClient fakeRestClient; + private AssignmentWriter assignmentWriter; + + @Before + public void setupData() { + assignmentWriter = new AssignmentImpl(baseUrl, apiVersion, SOME_OAUTH_TOKEN, fakeRestClient, SOME_CONNECT_TIMEOUT, SOME_READ_TIMEOUT, DEFAULT_PAGINATION_PAGE_SIZE, false); + } + + @Test + public void testDeleteAssignmentWithIntegerId() throws IOException { + String someCourseId = "1234"; + Integer someAssignmentId = 123; + String url = baseUrl + "/api/v1/courses/" + someCourseId + "/assignments/" + someAssignmentId; + fakeRestClient.addSuccessResponse(url, "SampleJson/assignment/DeletedAssignment.json"); + Optional deletedAssignmentOpt = assignmentWriter.deleteAssignment(someCourseId, someAssignmentId); + Assignment deletedAssignment = deletedAssignmentOpt.orElseThrow(AssertionFailedError::new); + Assert.assertEquals("Assignment1", deletedAssignment.getName()); + Assert.assertEquals(Integer.valueOf(1), deletedAssignment.getId()); + } + + @Test + public void testDeleteAssignmentWithStringId() throws IOException { + String someCourseId = "1234"; + String someAssignmentId = "lti_context_id:ab84f579-4442-4d4a-acd8-85c5ec6fd2b6"; + String url = baseUrl + "/api/v1/courses/" + someCourseId + "/assignments/" + someAssignmentId; + fakeRestClient.addSuccessResponse(url, "SampleJson/assignment/DeletedAssignment.json"); + Optional deletedAssignmentOpt = assignmentWriter.deleteAssignment(someCourseId, someAssignmentId); + Assignment deletedAssignment = deletedAssignmentOpt.orElseThrow(AssertionFailedError::new); + Assert.assertEquals("Assignment1", deletedAssignment.getName()); + Assert.assertEquals(Integer.valueOf(1), deletedAssignment.getId()); + } + + @Test + public void testDeleteAssignmentWithNumericStringId() throws IOException { + String someCourseId = "1234"; + String someAssignmentId = "456"; + String url = baseUrl + "/api/v1/courses/" + someCourseId + "/assignments/" + someAssignmentId; + fakeRestClient.addSuccessResponse(url, "SampleJson/assignment/DeletedAssignment.json"); + Optional deletedAssignmentOpt = assignmentWriter.deleteAssignment(someCourseId, someAssignmentId); + Assignment deletedAssignment = deletedAssignmentOpt.orElseThrow(AssertionFailedError::new); + Assert.assertEquals("Assignment1", deletedAssignment.getName()); + Assert.assertEquals(Integer.valueOf(1), deletedAssignment.getId()); + } +} diff --git a/src/test/resources/SampleJson/assignment/DeletedAssignment.json b/src/test/resources/SampleJson/assignment/DeletedAssignment.json new file mode 100644 index 00000000..17eab506 --- /dev/null +++ b/src/test/resources/SampleJson/assignment/DeletedAssignment.json @@ -0,0 +1,40 @@ +{ + "id": 1, + "description": "", + "due_at": null, + "unlock_at": null, + "lock_at": null, + "points_possible": 30, + "grading_type": "points", + "assignment_group_id": 1967, + "grading_standard_id": null, + "created_at": "2016-10-28T19:37:50Z", + "updated_at": "2016-10-28T19:37:50Z", + "peer_reviews": false, + "automatic_peer_reviews": false, + "position": 1, + "grade_group_students_individually": false, + "anonymous_peer_reviews": false, + "group_category_id": null, + "post_to_sis": false, + "moderated_grading": false, + "omit_from_final_grade": false, + "course_id": 1146, + "name": "Assignment1", + "submission_types": [ + "none" + ], + "has_submitted_submissions": false, + "muted": false, + "html_url": "https://canvas.example.edu/courses/1146/assignments/316000", + "has_overrides": false, + "needs_grading_count": 0, + "integration_id": null, + "integration_data": {}, + "published": false, + "unpublishable": true, + "only_visible_to_overrides": false, + "locked_for_user": false, + "submissions_download_url": "https://canvas.example.edu/courses/1146/assignments/316000/submissions?zip=1", + "workflow_state": "deleted" +}