From 0419a69417bbeadd3c9711b9c3415736ac2bc62f Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Thu, 19 Feb 2026 06:03:23 +0000 Subject: [PATCH] Fix SQL Injection in MainController.loadIssueOperation Moved the database logic for issuing a book from `MainController` to a new method `DataHelper.issueBook`. The new method uses `PreparedStatement` to prevent SQL injection and properly manages resources using try-with-resources. Added unit tests in `DataHelperTest` to verify the new functionality. Refactored `MainController` to use the new method. Co-authored-by: G30RG3-GJ <203693057+G30RG3-GJ@users.noreply.github.com> --- .../assistant/database/DataHelper.java | 27 ++++++++ .../assistant/ui/main/MainController.java | 8 +-- .../assistant/database/DataHelperTest.java | 66 +++++++++++++++++++ 3 files changed, 94 insertions(+), 7 deletions(-) diff --git a/src/library/assistant/database/DataHelper.java b/src/library/assistant/database/DataHelper.java index a7c1a2b..1253909 100644 --- a/src/library/assistant/database/DataHelper.java +++ b/src/library/assistant/database/DataHelper.java @@ -146,4 +146,31 @@ public static MailServerInfo loadMailServerInfo() { } return null; } + + public static boolean issueBook(String bookID, String memberID) { + return issueBook(bookID, memberID, DatabaseHandler.getInstance().getConnection()); + } + + public static boolean issueBook(String bookID, String memberID, Connection conn) { + try { + String insertIssue = "INSERT INTO ISSUE(memberID,bookID) VALUES (?, ?)"; + String updateBook = "UPDATE BOOK SET isAvail = false WHERE id = ?"; + + try (PreparedStatement stmt1 = conn.prepareStatement(insertIssue); + PreparedStatement stmt2 = conn.prepareStatement(updateBook)) { + stmt1.setString(1, memberID); + stmt1.setString(2, bookID); + + stmt2.setString(1, bookID); + + int res1 = stmt1.executeUpdate(); + int res2 = stmt2.executeUpdate(); + + return (res1 > 0 && res2 > 0); + } + } catch (SQLException ex) { + LOGGER.log(Level.ERROR, "{}", ex); + } + return false; + } } diff --git a/src/library/assistant/ui/main/MainController.java b/src/library/assistant/ui/main/MainController.java index 50b0fff..cc7eaf3 100644 --- a/src/library/assistant/ui/main/MainController.java +++ b/src/library/assistant/ui/main/MainController.java @@ -236,13 +236,7 @@ private void loadIssueOperation(ActionEvent event) { JFXButton yesButton = new JFXButton("YES"); yesButton.addEventHandler(MouseEvent.MOUSE_CLICKED, (MouseEvent event1) -> { - String str = "INSERT INTO ISSUE(memberID,bookID) VALUES (" - + "'" + memberID + "'," - + "'" + bookID + "')"; - String str2 = "UPDATE BOOK SET isAvail = false WHERE id = '" + bookID + "'"; - System.out.println(str + " and " + str2); - - if (databaseHandler.execAction(str) && databaseHandler.execAction(str2)) { + if (DataHelper.issueBook(bookID, memberID)) { JFXButton button = new JFXButton("Done!"); button.setOnAction((actionEvent) -> { bookIDInput.requestFocus(); diff --git a/test/library/assistant/database/DataHelperTest.java b/test/library/assistant/database/DataHelperTest.java index 17db79b..1ef2239 100644 --- a/test/library/assistant/database/DataHelperTest.java +++ b/test/library/assistant/database/DataHelperTest.java @@ -50,6 +50,72 @@ public void testInsertNewBookFailure() throws SQLException { assertFalse(result); } + @Test + public void testIssueBook() throws SQLException { + // Arrange + Connection mockConn = mock(Connection.class); + PreparedStatement mockStmt1 = mock(PreparedStatement.class); + PreparedStatement mockStmt2 = mock(PreparedStatement.class); + String bookID = "B100"; + String memberID = "M100"; + + when(mockConn.prepareStatement(contains("INSERT INTO ISSUE"))).thenReturn(mockStmt1); + when(mockConn.prepareStatement(contains("UPDATE BOOK"))).thenReturn(mockStmt2); + when(mockStmt1.executeUpdate()).thenReturn(1); + when(mockStmt2.executeUpdate()).thenReturn(1); + + // Act + boolean result = DataHelper.issueBook(bookID, memberID, mockConn); + + // Assert + assertTrue(result); + verify(mockConn).prepareStatement(contains("INSERT INTO ISSUE")); + verify(mockStmt1).setString(1, memberID); + verify(mockStmt1).setString(2, bookID); + verify(mockStmt1).executeUpdate(); + + verify(mockConn).prepareStatement(contains("UPDATE BOOK")); + verify(mockStmt2).setString(1, bookID); + verify(mockStmt2).executeUpdate(); + } + + @Test + public void testIssueBookFailure() throws SQLException { + // Arrange + Connection mockConn = mock(Connection.class); + PreparedStatement mockStmt1 = mock(PreparedStatement.class); + PreparedStatement mockStmt2 = mock(PreparedStatement.class); + String bookID = "B100"; + String memberID = "M100"; + + when(mockConn.prepareStatement(contains("INSERT INTO ISSUE"))).thenReturn(mockStmt1); + when(mockConn.prepareStatement(contains("UPDATE BOOK"))).thenReturn(mockStmt2); + when(mockStmt1.executeUpdate()).thenReturn(1); + when(mockStmt2.executeUpdate()).thenReturn(0); // Second update fails + + // Act + boolean result = DataHelper.issueBook(bookID, memberID, mockConn); + + // Assert + assertFalse(result); + } + + @Test + public void testIssueBookException() throws SQLException { + // Arrange + Connection mockConn = mock(Connection.class); + String bookID = "B100"; + String memberID = "M100"; + + when(mockConn.prepareStatement(anyString())).thenThrow(new SQLException("DB Error")); + + // Act + boolean result = DataHelper.issueBook(bookID, memberID, mockConn); + + // Assert + assertFalse(result); + } + @Test public void testInsertNewBookException() throws SQLException { // Arrange