diff --git a/libs/test/byte-buddy-1.12.19.jar b/libs/test/byte-buddy-1.12.19.jar new file mode 100644 index 0000000..2cd272d Binary files /dev/null and b/libs/test/byte-buddy-1.12.19.jar differ diff --git a/libs/test/byte-buddy-agent-1.12.19.jar b/libs/test/byte-buddy-agent-1.12.19.jar new file mode 100644 index 0000000..d067982 Binary files /dev/null and b/libs/test/byte-buddy-agent-1.12.19.jar differ diff --git a/libs/test/greenmail-standalone-1.6.15.jar b/libs/test/greenmail-standalone-1.6.15.jar new file mode 100644 index 0000000..88f6463 Binary files /dev/null and b/libs/test/greenmail-standalone-1.6.15.jar differ diff --git a/libs/test/hamcrest-core-1.3.jar b/libs/test/hamcrest-core-1.3.jar new file mode 100644 index 0000000..9d5fe16 Binary files /dev/null and b/libs/test/hamcrest-core-1.3.jar differ diff --git a/libs/test/junit-4.13.2.jar b/libs/test/junit-4.13.2.jar new file mode 100644 index 0000000..6da55d8 Binary files /dev/null and b/libs/test/junit-4.13.2.jar differ diff --git a/libs/test/mockito-core-4.11.0.jar b/libs/test/mockito-core-4.11.0.jar new file mode 100644 index 0000000..46eff14 Binary files /dev/null and b/libs/test/mockito-core-4.11.0.jar differ diff --git a/libs/test/objenesis-3.3.jar b/libs/test/objenesis-3.3.jar new file mode 100644 index 0000000..d660190 Binary files /dev/null and b/libs/test/objenesis-3.3.jar differ diff --git a/nbproject/project.properties b/nbproject/project.properties index 49ba83d..f2ab788 100644 --- a/nbproject/project.properties +++ b/nbproject/project.properties @@ -89,7 +89,10 @@ javac.source=1.8 javac.target=1.8 javac.test.classpath=\ ${javac.classpath}:\ - ${build.classes.dir} + ${build.classes.dir}:\ + libs/test/junit-4.13.2.jar:\ + libs/test/hamcrest-core-1.3.jar:\ + libs/test/greenmail-standalone-1.6.15.jar javac.test.modulepath=\ ${javac.modulepath} javac.test.processorpath=\ diff --git a/run_tests.sh b/run_tests.sh new file mode 100755 index 0000000..8602f99 --- /dev/null +++ b/run_tests.sh @@ -0,0 +1,13 @@ +#!/bin/bash +mkdir -p build/test/stubs +mkdir -p build/test/classes + +javac -d build/test/stubs test/stubs/library/assistant/database/DatabaseHandler.java test/stubs/library/assistant/ui/listmember/MemberListController.java + +javac -sourcepath "" -cp "libs/*:libs/test/*:build/test/stubs" -d build/test/classes \ +src/library/assistant/data/model/Book.java \ +src/library/assistant/data/model/MailServerInfo.java \ +src/library/assistant/database/DataHelper.java \ +test/library/assistant/database/DataHelperTest.java + +java -cp "build/test/classes:build/test/stubs:libs/*:libs/test/*" org.junit.runner.JUnitCore library.assistant.database.DataHelperTest diff --git a/src/library/assistant/alert/AlertMaker.java b/src/library/assistant/alert/AlertMaker.java index ebdb581..cb690c4 100644 --- a/src/library/assistant/alert/AlertMaker.java +++ b/src/library/assistant/alert/AlertMaker.java @@ -47,36 +47,7 @@ public static void showErrorMessage(String title, String content) { } public static void showErrorMessage(Exception ex) { - Alert alert = new Alert(AlertType.ERROR); - alert.setTitle("Error occured"); - alert.setHeaderText("Error Occured"); - alert.setContentText(ex.getLocalizedMessage()); - - StringWriter sw = new StringWriter(); - PrintWriter pw = new PrintWriter(sw); - ex.printStackTrace(pw); - String exceptionText = sw.toString(); - - Label label = new Label("The exception stacktrace was:"); - - TextArea textArea = new TextArea(exceptionText); - textArea.setEditable(false); - textArea.setWrapText(true); - - textArea.setMaxWidth(Double.MAX_VALUE); - textArea.setMaxHeight(Double.MAX_VALUE); - GridPane.setVgrow(textArea, Priority.ALWAYS); - GridPane.setHgrow(textArea, Priority.ALWAYS); - - GridPane expContent = new GridPane(); - expContent.setMaxWidth(Double.MAX_VALUE); - expContent.add(label, 0, 0); - expContent.add(textArea, 0, 1); - - alert.getDialogPane().setExpandableContent(expContent); - - styleAlert(alert); - alert.showAndWait(); + showErrorMessage(ex, "Error Occured", ex.getLocalizedMessage()); } public static void showErrorMessage(Exception ex, String title, String content) { @@ -107,6 +78,7 @@ public static void showErrorMessage(Exception ex, String title, String content) expContent.add(textArea, 0, 1); alert.getDialogPane().setExpandableContent(expContent); + styleAlert(alert); alert.showAndWait(); } diff --git a/src/library/assistant/database/DataHelper.java b/src/library/assistant/database/DataHelper.java index 5775518..a7c1a2b 100644 --- a/src/library/assistant/database/DataHelper.java +++ b/src/library/assistant/database/DataHelper.java @@ -1,5 +1,6 @@ package library.assistant.database; +import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; @@ -20,8 +21,12 @@ public class DataHelper { private final static Logger LOGGER = LogManager.getLogger(DatabaseHandler.class.getName()); public static boolean insertNewBook(Book book) { + return insertNewBook(book, DatabaseHandler.getInstance().getConnection()); + } + + public static boolean insertNewBook(Book book, Connection conn) { try { - PreparedStatement statement = DatabaseHandler.getInstance().getConnection().prepareStatement( + PreparedStatement statement = conn.prepareStatement( "INSERT INTO BOOK(id,title,author,publisher,isAvail) VALUES(?,?,?,?,?)"); statement.setString(1, book.getId()); statement.setString(2, book.getTitle()); diff --git a/src/library/assistant/encryption/EncryptionUtil.java b/src/library/assistant/encryption/EncryptionUtil.java index 1497add..023c2d7 100644 --- a/src/library/assistant/encryption/EncryptionUtil.java +++ b/src/library/assistant/encryption/EncryptionUtil.java @@ -5,11 +5,8 @@ import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; -import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import java.util.Arrays; -import java.util.Random; +import java.security.SecureRandom; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import javax.crypto.Cipher; @@ -117,11 +114,10 @@ private static byte[] generateSecureKey() throws NoSuchAlgorithmException { return data; } - private static byte[] prepareIV() throws NoSuchAlgorithmException { - MessageDigest digest = MessageDigest.getInstance("SHA-512"); - String randomVal = String.valueOf(new Random(System.currentTimeMillis()).nextLong()); - byte[] hash = digest.digest(randomVal.getBytes(StandardCharsets.UTF_8)); - return Arrays.copyOfRange(hash, 0, 16); + private static byte[] prepareIV() { + byte[] iv = new byte[16]; + new SecureRandom().nextBytes(iv); + return iv; } private static void writeKey(CipherSpec spec) throws Exception { diff --git a/src/library/assistant/ui/addmember/MemberAddLoader.java b/src/library/assistant/ui/addmember/MemberAddLoader.java index b1f6ae3..cef93af 100644 --- a/src/library/assistant/ui/addmember/MemberAddLoader.java +++ b/src/library/assistant/ui/addmember/MemberAddLoader.java @@ -6,7 +6,6 @@ package library.assistant.ui.addmember; import javafx.application.Application; -import static javafx.application.Application.launch; import javafx.fxml.FXMLLoader; import javafx.scene.Parent; import javafx.scene.Scene; @@ -25,8 +24,4 @@ public void start(Stage stage) throws Exception { stage.show(); } - public static void main(String[] args) { - launch(args); - } - } diff --git a/src/library/assistant/ui/issuedlist/IssuedListController.java b/src/library/assistant/ui/issuedlist/IssuedListController.java index 27dbfd3..7f5dec5 100644 --- a/src/library/assistant/ui/issuedlist/IssuedListController.java +++ b/src/library/assistant/ui/issuedlist/IssuedListController.java @@ -10,6 +10,7 @@ import java.util.List; import java.util.ResourceBundle; import java.util.concurrent.TimeUnit; +import javafx.concurrent.Task; import javafx.beans.property.SimpleFloatProperty; import javafx.beans.property.SimpleIntegerProperty; import javafx.beans.property.SimpleStringProperty; @@ -81,31 +82,45 @@ public void setBookReturnCallback(BookReturnCallback callback) { private void loadData() { list.clear(); - DatabaseHandler handler = DatabaseHandler.getInstance(); - String qu = "SELECT ISSUE.bookID, ISSUE.memberID, ISSUE.issueTime, MEMBER.name, BOOK.title FROM ISSUE\n" - + "LEFT OUTER JOIN MEMBER\n" - + "ON MEMBER.id = ISSUE.memberID\n" - + "LEFT OUTER JOIN BOOK\n" - + "ON BOOK.id = ISSUE.bookID"; - ResultSet rs = handler.execQuery(qu); - Preferences pref = Preferences.getPreferences(); - try { - int counter = 0; - while (rs.next()) { - counter += 1; - String memberName = rs.getString("name"); - String bookID = rs.getString("bookID"); - String bookTitle = rs.getString("title"); - Timestamp issueTime = rs.getTimestamp("issueTime"); - System.out.println("Issued on " + issueTime); - Integer days = Math.toIntExact(TimeUnit.MILLISECONDS.toDays(System.currentTimeMillis() - issueTime.getTime())) + 1; - Float fine = LibraryAssistantUtil.getFineAmount(days); - IssueInfo issueInfo = new IssueInfo(counter, bookID, bookTitle, memberName, LibraryAssistantUtil.formatDateTimeString(new Date(issueTime.getTime())), days, fine); - list.add(issueInfo); + + Task> task = new Task>() { + @Override + protected List call() throws Exception { + List tempList = new ArrayList<>(); + DatabaseHandler handler = DatabaseHandler.getInstance(); + String qu = "SELECT ISSUE.bookID, ISSUE.memberID, ISSUE.issueTime, MEMBER.name, BOOK.title FROM ISSUE\n" + + "LEFT OUTER JOIN MEMBER\n" + + "ON MEMBER.id = ISSUE.memberID\n" + + "LEFT OUTER JOIN BOOK\n" + + "ON BOOK.id = ISSUE.bookID"; + ResultSet rs = handler.execQuery(qu); + Preferences pref = Preferences.getPreferences(); + try { + int counter = 0; + while (rs.next()) { + counter += 1; + String memberName = rs.getString("name"); + String bookID = rs.getString("bookID"); + String bookTitle = rs.getString("title"); + Timestamp issueTime = rs.getTimestamp("issueTime"); + System.out.println("Issued on " + issueTime); + Integer days = Math.toIntExact(TimeUnit.MILLISECONDS.toDays(System.currentTimeMillis() - issueTime.getTime())) + 1; + Float fine = LibraryAssistantUtil.getFineAmount(days, pref); + IssueInfo issueInfo = new IssueInfo(counter, bookID, bookTitle, memberName, LibraryAssistantUtil.formatDateTimeString(new Date(issueTime.getTime())), days, fine); + tempList.add(issueInfo); + } + } catch (SQLException ex) { + ex.printStackTrace(); + } + return tempList; } - } catch (SQLException ex) { - ex.printStackTrace(); - } + }; + + task.setOnSucceeded(event -> { + list.setAll(task.getValue()); + }); + + new Thread(task).start(); } @FXML diff --git a/src/library/assistant/ui/issuedlist/IssuedListLoader.java b/src/library/assistant/ui/issuedlist/IssuedListLoader.java index dda8f74..ee9a592 100644 --- a/src/library/assistant/ui/issuedlist/IssuedListLoader.java +++ b/src/library/assistant/ui/issuedlist/IssuedListLoader.java @@ -2,7 +2,6 @@ import javafx.application.Application; -import static javafx.application.Application.launch; import javafx.fxml.FXMLLoader; import javafx.scene.Parent; import javafx.scene.Scene; @@ -21,8 +20,4 @@ public void start(Stage stage) throws Exception { stage.show(); } - public static void main(String[] args) { - launch(args); - } - } diff --git a/src/library/assistant/ui/listbook/BookListLoader.java b/src/library/assistant/ui/listbook/BookListLoader.java index df2c6b8..f3fa4e5 100644 --- a/src/library/assistant/ui/listbook/BookListLoader.java +++ b/src/library/assistant/ui/listbook/BookListLoader.java @@ -2,7 +2,6 @@ import javafx.application.Application; -import static javafx.application.Application.launch; import javafx.fxml.FXMLLoader; import javafx.scene.Parent; import javafx.scene.Scene; @@ -21,8 +20,4 @@ public void start(Stage stage) throws Exception { stage.show(); } - public static void main(String[] args) { - launch(args); - } - } diff --git a/src/library/assistant/ui/listmember/MemberListLoader.java b/src/library/assistant/ui/listmember/MemberListLoader.java index fdf9ef2..994ccda 100644 --- a/src/library/assistant/ui/listmember/MemberListLoader.java +++ b/src/library/assistant/ui/listmember/MemberListLoader.java @@ -2,7 +2,6 @@ package library.assistant.ui.listmember; import javafx.application.Application; -import static javafx.application.Application.launch; import javafx.fxml.FXMLLoader; import javafx.scene.Parent; import javafx.scene.Scene; @@ -21,8 +20,4 @@ public void start(Stage stage) throws Exception { stage.show(); } - public static void main(String[] args) { - launch(args); - } - } diff --git a/src/library/assistant/ui/notifoverdue/OverdueNotificationLoader.java b/src/library/assistant/ui/notifoverdue/OverdueNotificationLoader.java index e765bbf..26acf66 100644 --- a/src/library/assistant/ui/notifoverdue/OverdueNotificationLoader.java +++ b/src/library/assistant/ui/notifoverdue/OverdueNotificationLoader.java @@ -1,7 +1,6 @@ package library.assistant.ui.notifoverdue; import javafx.application.Application; -import static javafx.application.Application.launch; import javafx.fxml.FXMLLoader; import javafx.scene.Parent; import javafx.scene.Scene; @@ -19,8 +18,4 @@ public void start(Stage stage) throws Exception { stage.show(); } - public static void main(String[] args) { - launch(args); - } - } diff --git a/src/library/assistant/ui/settings/SettingsLoader.java b/src/library/assistant/ui/settings/SettingsLoader.java index 33f4376..7464be4 100644 --- a/src/library/assistant/ui/settings/SettingsLoader.java +++ b/src/library/assistant/ui/settings/SettingsLoader.java @@ -1,7 +1,6 @@ package library.assistant.ui.settings; import javafx.application.Application; -import static javafx.application.Application.launch; import javafx.fxml.FXMLLoader; import javafx.scene.Parent; import javafx.scene.Scene; @@ -25,8 +24,4 @@ public void start(Stage stage) throws Exception { }).start(); } - public static void main(String[] args) { - launch(args); - } - } diff --git a/src/library/assistant/util/LibraryAssistantUtil.java b/src/library/assistant/util/LibraryAssistantUtil.java index fe61a73..b9d37a0 100644 --- a/src/library/assistant/util/LibraryAssistantUtil.java +++ b/src/library/assistant/util/LibraryAssistantUtil.java @@ -62,6 +62,10 @@ public static Object loadWindow(URL loc, String title, Stage parentStage) { public static Float getFineAmount(int totalDays) { Preferences pref = Preferences.getPreferences(); + return getFineAmount(totalDays, pref); + } + + public static Float getFineAmount(int totalDays, Preferences pref) { Integer fineDays = totalDays - pref.getnDaysWithoutFine(); Float fine = 0f; if (fineDays > 0) { diff --git a/test/library/assistant/database/DataHelperTest.java b/test/library/assistant/database/DataHelperTest.java new file mode 100644 index 0000000..17db79b --- /dev/null +++ b/test/library/assistant/database/DataHelperTest.java @@ -0,0 +1,67 @@ +package library.assistant.database; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import library.assistant.data.model.Book; +import org.junit.Test; +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +public class DataHelperTest { + + @Test + public void testInsertNewBook() throws SQLException { + // Arrange + Connection mockConn = mock(Connection.class); + PreparedStatement mockStmt = mock(PreparedStatement.class); + Book book = new Book("B100", "Test Title", "Test Author", "Test Publisher", true); + + when(mockConn.prepareStatement(anyString())).thenReturn(mockStmt); + when(mockStmt.executeUpdate()).thenReturn(1); + + // Act + boolean result = DataHelper.insertNewBook(book, mockConn); + + // Assert + assertTrue(result); + verify(mockStmt).setString(1, "B100"); + verify(mockStmt).setString(2, "Test Title"); + verify(mockStmt).setString(3, "Test Author"); + verify(mockStmt).setString(4, "Test Publisher"); + verify(mockStmt).setBoolean(5, true); + verify(mockStmt).executeUpdate(); + } + + @Test + public void testInsertNewBookFailure() throws SQLException { + // Arrange + Connection mockConn = mock(Connection.class); + PreparedStatement mockStmt = mock(PreparedStatement.class); + Book book = new Book("B100", "Test Title", "Test Author", "Test Publisher", true); + + when(mockConn.prepareStatement(anyString())).thenReturn(mockStmt); + when(mockStmt.executeUpdate()).thenReturn(0); + + // Act + boolean result = DataHelper.insertNewBook(book, mockConn); + + // Assert + assertFalse(result); + } + + @Test + public void testInsertNewBookException() throws SQLException { + // Arrange + Connection mockConn = mock(Connection.class); + Book book = new Book("B100", "Test Title", "Test Author", "Test Publisher", true); + + when(mockConn.prepareStatement(anyString())).thenThrow(new SQLException("DB Error")); + + // Act + boolean result = DataHelper.insertNewBook(book, mockConn); + + // Assert + assertFalse(result); + } +} diff --git a/test/library/assistant/email/EmailUtilTest.java b/test/library/assistant/email/EmailUtilTest.java new file mode 100644 index 0000000..9613abc --- /dev/null +++ b/test/library/assistant/email/EmailUtilTest.java @@ -0,0 +1,86 @@ +package library.assistant.email; + +import com.icegreen.greenmail.util.GreenMail; +import com.icegreen.greenmail.util.ServerSetup; +import java.util.Properties; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import javax.mail.Message; +import javax.mail.Session; +import javax.mail.internet.MimeMessage; +import library.assistant.data.callback.GenericCallback; +import library.assistant.data.model.MailServerInfo; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +public class EmailUtilTest { + + private GreenMail greenMail; + // Use an arbitrary port or find a free one. 3025 is default for GreenMail SMTP. + private static final int SMTP_PORT = 3025; + + @Before + public void setUp() { + ServerSetup setup = new ServerSetup(SMTP_PORT, "localhost", ServerSetup.PROTOCOL_SMTP); + greenMail = new GreenMail(setup); + greenMail.setUser("test@library.com", "password"); + greenMail.start(); + } + + @After + public void tearDown() { + if (greenMail != null) { + greenMail.stop(); + } + } + + @Test + public void testSendMail() throws Exception { + // Prepare mail server info pointing to our local GreenMail instance + // SSL disabled for simplicity in this test + MailServerInfo mailServerInfo = new MailServerInfo("localhost", SMTP_PORT, "test@library.com", "password", false); + + String recipient = "user@example.com"; + String subject = "Test Subject"; + String content = "

Test Content

"; + + // Use a latch to wait for the async operation + CountDownLatch latch = new CountDownLatch(1); + final boolean[] success = {false}; + + GenericCallback callback = new GenericCallback() { + @Override + public Object taskCompleted(Object val) { + if (val instanceof Boolean) { + success[0] = (Boolean) val; + } + latch.countDown(); + return null; + } + }; + + // Invoke the method under test + EmailUtil.sendMail(mailServerInfo, recipient, content, subject, callback); + + // Wait for the email sending thread to complete + boolean completed = latch.await(10, TimeUnit.SECONDS); + + Assert.assertTrue("Timeout waiting for email to be sent", completed); + Assert.assertTrue("Callback should return true indicating success", success[0]); + + // Verify email reception on GreenMail + MimeMessage[] receivedMessages = greenMail.getReceivedMessages(); + Assert.assertEquals("Should have received one email", 1, receivedMessages.length); + + MimeMessage received = receivedMessages[0]; + Assert.assertEquals(subject, received.getSubject()); + Assert.assertEquals(recipient, received.getRecipients(Message.RecipientType.TO)[0].toString()); + // Simple check for content presence + // Note: content might be multipart depending on implementation details + // In EmailUtil: message.setContent(content, "text/html"); + // So it should be text/html. + Assert.assertTrue(received.getContent().toString().contains("Test Content")); + } +} diff --git a/test/stubs/library/assistant/database/DatabaseHandler.java b/test/stubs/library/assistant/database/DatabaseHandler.java new file mode 100644 index 0000000..4cf2fbb --- /dev/null +++ b/test/stubs/library/assistant/database/DatabaseHandler.java @@ -0,0 +1,26 @@ +package library.assistant.database; + +import java.sql.Connection; + +public class DatabaseHandler { + private static DatabaseHandler handler = null; + private Connection conn = null; + + private DatabaseHandler() {} + + public static DatabaseHandler getInstance() { + if (handler == null) { + handler = new DatabaseHandler(); + } + return handler; + } + + public Connection getConnection() { + return conn; + } + + // Helper method for testing + public void setConnection(Connection conn) { + this.conn = conn; + } +} diff --git a/test/stubs/library/assistant/ui/listmember/MemberListController.java b/test/stubs/library/assistant/ui/listmember/MemberListController.java new file mode 100644 index 0000000..e6fe0e3 --- /dev/null +++ b/test/stubs/library/assistant/ui/listmember/MemberListController.java @@ -0,0 +1,33 @@ +package library.assistant.ui.listmember; + +public class MemberListController { + public static class Member { + private String id; + private String name; + private String mobile; + private String email; + + public Member(String name, String id, String mobile, String email) { + this.name = name; + this.id = id; + this.mobile = mobile; + this.email = email; + } + + public String getName() { + return name; + } + + public String getId() { + return id; + } + + public String getMobile() { + return mobile; + } + + public String getEmail() { + return email; + } + } +}