From 79b0b976f15778e241a7d0b0ec43c3e332298dfe Mon Sep 17 00:00:00 2001 From: https-tesi Date: Wed, 14 Jan 2026 20:21:10 +0100 Subject: [PATCH 1/3] BorrowerTest.java --- Project/src/LMS/Tests/BorrowerTest.java | 56 +++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 Project/src/LMS/Tests/BorrowerTest.java diff --git a/Project/src/LMS/Tests/BorrowerTest.java b/Project/src/LMS/Tests/BorrowerTest.java new file mode 100644 index 0000000..2bac50d --- /dev/null +++ b/Project/src/LMS/Tests/BorrowerTest.java @@ -0,0 +1,56 @@ +package LMS.Tests; + +import LMS.*; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.nio.charset.StandardCharsets; +import java.util.Date; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Unit tests for the Borrower class. + */ +public class BorrowerTest { + + @BeforeEach + void resetState() { + // reset the global ID counter so IDs start from 1 for each test + Person.setIDCount(0); + } + + @Test + void addAndRemoveBorrowedBook_updatesBorrowedList() { + Borrower borrower = new Borrower(-1, "Alice", "Addr", 12345); + assertTrue(borrower.getBorrowedBooks().isEmpty()); + + // create supporting objects + Book book = new Book(-1, "Clean Code", "SE", "Robert Martin", false); + Clerk clerk = new Clerk(-1, "Clerk", "Desk", 11111, 300.0, -1); + Loan loan = new Loan(borrower, book, clerk, null, new Date(), null, false); + + borrower.addBorrowedBook(loan); + assertEquals(1, borrower.getBorrowedBooks().size()); + assertSame(loan, borrower.getBorrowedBooks().get(0)); + + borrower.removeBorrowedBook(loan); + assertTrue(borrower.getBorrowedBooks().isEmpty()); + } + + @Test + void addAndRemoveHoldRequest_updatesHoldList() { + Borrower borrower = new Borrower(-1, "Bob", "Addr", 55555); + assertTrue(borrower.getOnHoldBooks().isEmpty()); + + // Since HoldRequest classes are unavailable, use null as a placeholder + borrower.addHoldRequest(null); + assertEquals(1, borrower.getOnHoldBooks().size()); + assertNull(borrower.getOnHoldBooks().get(0)); + + borrower.removeHoldRequest(null); + assertTrue(borrower.getOnHoldBooks().isEmpty()); + } +} From 9e482a83de41c79351d662d7cef44c7b8449cf2a Mon Sep 17 00:00:00 2001 From: https-tesi Date: Wed, 14 Jan 2026 20:22:15 +0100 Subject: [PATCH 2/3] Test for Clerk class --- Project/src/LMS/Tests/ClerkTest.java | 40 ++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 Project/src/LMS/Tests/ClerkTest.java diff --git a/Project/src/LMS/Tests/ClerkTest.java b/Project/src/LMS/Tests/ClerkTest.java new file mode 100644 index 0000000..c9b6ffe --- /dev/null +++ b/Project/src/LMS/Tests/ClerkTest.java @@ -0,0 +1,40 @@ +package LMS.Tests; + +import LMS.Clerk; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Unit tests for the Clerk class. + */ +public class ClerkTest { + + @BeforeEach + void resetDeskCounter() { + // reset the static desk counter so auto‑assignment starts at 0 + Clerk.currentdeskNumber = 0; + } + + @Test + void constructorAutoDesk_assignsSequentialNumbers() { + Clerk c1 = new Clerk(-1, "Alice", "Addr", 123, 500.0, -1); + Clerk c2 = new Clerk(-1, "Bob", "Addr", 456, 600.0, -1); + assertEquals(0, c1.deskNo); + assertEquals(1, c2.deskNo); + } + + @Test + void constructorExplicitDesk_usesProvidedNumber() { + Clerk c = new Clerk(-1, "Clara", "Addr", 789, 700.0, 10); + assertEquals(10, c.deskNo); + } + + @Test + void getSalary_returnsAssignedSalary() { + Clerk c = new Clerk(-1, "Dave", "Addr", 111, 550.0, -1); + assertEquals(550.0, c.getSalary()); + } +} + From 0a56d48c2e99967ef80061cc6f51d25e56ac98db Mon Sep 17 00:00:00 2001 From: https-tesi Date: Fri, 16 Jan 2026 12:33:48 +0100 Subject: [PATCH 3/3] unit tests done --- Project/src/LMS/Clerk.java | 15 +- Project/src/LMS/Librarian.java | 2 +- Project/src/LMS/Library.java | 1204 ++++++++++---------- Project/src/LMS/Person.java | 2 +- Project/src/LMS/Tests/BorrowerTest.java | 56 - Project/src/Tests/BorrowerTest.java | 106 ++ Project/src/{LMS => }/Tests/ClerkTest.java | 12 +- Project/src/Tests/LibrarianTest.java | 71 ++ Project/src/Tests/LibraryTest.java | 416 +++++++ Project/src/Tests/PersonTest.java | 104 ++ 10 files changed, 1317 insertions(+), 671 deletions(-) delete mode 100644 Project/src/LMS/Tests/BorrowerTest.java create mode 100644 Project/src/Tests/BorrowerTest.java rename Project/src/{LMS => }/Tests/ClerkTest.java (71%) create mode 100644 Project/src/Tests/LibrarianTest.java create mode 100644 Project/src/Tests/LibraryTest.java create mode 100644 Project/src/Tests/PersonTest.java diff --git a/Project/src/LMS/Clerk.java b/Project/src/LMS/Clerk.java index a55c14e..37a4dd7 100644 --- a/Project/src/LMS/Clerk.java +++ b/Project/src/LMS/Clerk.java @@ -1,15 +1,14 @@ - package LMS; public class Clerk extends Staff { - - int deskNo; //Desk Number of the Clerk + + public int deskNo; //Desk Number of the Clerk public static int currentdeskNumber = 0; - + public Clerk(int id, String n, String a,int ph, double s,int dk) // para cons. { super(id,n,a,ph,s); - + if(dk == -1) { deskNo = currentdeskNumber; @@ -18,10 +17,10 @@ public Clerk(int id, String n, String a,int ph, double s,int dk) // para cons. { deskNo=dk; } - + currentdeskNumber++; } - + // Printing Clerk's Info @Override public void printInfo() @@ -29,5 +28,5 @@ public void printInfo() super.printInfo(); System.out.println("Desk Number: " + deskNo); } - + } // Clerk's Class Closed \ No newline at end of file diff --git a/Project/src/LMS/Librarian.java b/Project/src/LMS/Librarian.java index d710740..ff01cc4 100644 --- a/Project/src/LMS/Librarian.java +++ b/Project/src/LMS/Librarian.java @@ -6,7 +6,7 @@ public class Librarian extends Staff { - int officeNo; //Office Number of the Librarian + public int officeNo; //Office Number of the Librarian public static int currentOfficeNumber = 0; public Librarian(int id, String n, String a, int p, double s, int of) // para cons. diff --git a/Project/src/LMS/Library.java b/Project/src/LMS/Library.java index 76a548f..ba1c8d9 100644 --- a/Project/src/LMS/Library.java +++ b/Project/src/LMS/Library.java @@ -18,22 +18,22 @@ public class Library { - + private String name; // name of library public static Librarian librarian; // object of Librarian (only one) public static ArrayList persons; // all clerks and borrowers private ArrayList booksInLibrary; // all books in library are here! - + private ArrayList loans; // history of all books which have been issued - + public int book_return_deadline; //return deadline after which fine will be generated each day public double per_day_fine; - + public int hold_request_expiry; //number of days after which a hold request will expire //Created object of the hold request operations private HoldRequestOperations holdRequestsOperations =new HoldRequestOperations(); - + /*----Following Singleton Design Pattern (Lazy Instantiation)------------*/ private static Library obj; @@ -43,24 +43,24 @@ public static Library getInstance() { obj = new Library(); } - + return obj; } /*---------------------------------------------------------------------*/ - + private Library() // default cons. { name = null; librarian = null; persons = new ArrayList(); - + booksInLibrary = new ArrayList(); loans = new ArrayList(); } - + /*------------Setter FUNCs.------------*/ - + public void setReturnDeadline(int deadline) { book_return_deadline = deadline; @@ -75,33 +75,33 @@ public void setRequestExpiry(int hrExpiry) { hold_request_expiry = hrExpiry; } - /*--------------------------------------*/ - - - + /*--------------------------------------*/ + + + // Setter Func. - public void setName(String n) + public void setName(String n) { name = n; } - + /*-----------Getter FUNCs.------------*/ - + public int getHoldRequestExpiry() { return hold_request_expiry; } - + public ArrayList getPersons() { return persons; } - + public Librarian getLibrarian() { return librarian; } - + public String getLibraryName() { return name; @@ -111,7 +111,7 @@ public ArrayList getBooks() { return booksInLibrary; } - + /*---------------------------------------*/ /*-----Adding other People in Library----*/ @@ -126,23 +126,23 @@ public void addBorrower(Borrower b) persons.add(b); } - + public void addLoan(Loan l) { loans.add(l); } - + /*----------------------------------------------*/ - + /*-----------Finding People in Library--------------*/ public Borrower findBorrower() { System.out.println("\nEnter Borrower's ID: "); - + int id = 0; - + Scanner scanner = new Scanner(System.in); - + try{ id = scanner.nextInt(); } @@ -156,19 +156,19 @@ public Borrower findBorrower() if (persons.get(i).getID() == id && persons.get(i).getClass().getSimpleName().equals("Borrower")) return (Borrower)(persons.get(i)); } - + System.out.println("\nSorry this ID didn't match any Borrower's ID."); return null; } - + public Clerk findClerk() { System.out.println("\nEnter Clerk's ID: "); - + int id = 0; - + Scanner scanner = new Scanner(System.in); - + try{ id = scanner.nextInt(); } @@ -182,33 +182,33 @@ public Clerk findClerk() if (persons.get(i).getID() == id && persons.get(i).getClass().getSimpleName().equals("Clerk")) return (Clerk)(persons.get(i)); } - + System.out.println("\nSorry this ID didn't match any Clerk's ID."); return null; } - + /*------- FUNCS. on Books In Library--------------*/ public void addBookinLibrary(Book b) { booksInLibrary.add(b); } - + //When this function is called, only the pointer of the book placed in booksInLibrary is removed. But the real object of book //is still there in memory because pointers of that book placed in IssuedBooks and ReturnedBooks are still pointing to that book. And we //are maintaining those pointers so that we can maintain history. //But if we donot want to maintain history then we can delete those pointers placed in IssuedBooks and ReturnedBooks as well which are //pointing to that book. In this way the book will be really removed from memory. - public void removeBookfromLibrary(Book b) + public void removeBookfromLibrary(Book b) { boolean delete = true; - + //Checking if this book is currently borrowed by some borrower for (int i = 0; i < persons.size() && delete; i++) { if (persons.get(i).getClass().getSimpleName().equals("Borrower")) { ArrayList borBooks = ((Borrower)(persons.get(i))).getBorrowedBooks(); - + for (int j = 0; j < borBooks.size() && delete; j++) { if (borBooks.get(j).getBook() == b) @@ -216,33 +216,33 @@ public void removeBookfromLibrary(Book b) delete = false; System.out.println("This particular book is currently borrowed by some borrower."); } - } + } } } - + if (delete) { System.out.println("\nCurrently this book is not borrowed by anyone."); ArrayList hRequests = b.getHoldRequests(); - + if(!hRequests.isEmpty()) { System.out.println("\nThis book might be on hold requests by some borrowers. Deleting this book will delete the relevant hold requests too."); System.out.println("Do you still want to delete the book? (y/n)"); - + Scanner sc = new Scanner(System.in); - + while (true) { String choice = sc.next(); - + if(choice.equals("y") || choice.equals("n")) { if(choice.equals("n")) { System.out.println("\nDelete Unsuccessful."); return; - } + } else { //Empty the books hold request array @@ -258,34 +258,34 @@ public void removeBookfromLibrary(Book b) else System.out.println("Invalid Input. Enter (y/n): "); } - + } else System.out.println("This book has no hold requests."); - + booksInLibrary.remove(b); System.out.println("The book is successfully removed."); } else System.out.println("\nDelete Unsuccessful."); } - - - - // Searching Books on basis of title, Subject or Author + + + + // Searching Books on basis of title, Subject or Author public ArrayList searchForBooks() throws IOException { String choice; String title = "", subject = "", author = ""; - - Scanner sc = new Scanner(System.in); + + Scanner sc = new Scanner(System.in); BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); - + while (true) { - System.out.println("\nEnter either '1' or '2' or '3' for search by Title, Subject or Author of Book respectively: "); + System.out.println("\nEnter either '1' or '2' or '3' for search by Title, Subject or Author of Book respectively: "); choice = sc.next(); - + if (choice.equals("1") || choice.equals("2") || choice.equals("3")) break; else @@ -294,62 +294,62 @@ public ArrayList searchForBooks() throws IOException if (choice.equals("1")) { - System.out.println("\nEnter the Title of the Book: "); - title = reader.readLine(); + System.out.println("\nEnter the Title of the Book: "); + title = reader.readLine(); } else if (choice.equals("2")) { - System.out.println("\nEnter the Subject of the Book: "); - subject = reader.readLine(); + System.out.println("\nEnter the Subject of the Book: "); + subject = reader.readLine(); } - + else { - System.out.println("\nEnter the Author of the Book: "); - author = reader.readLine(); + System.out.println("\nEnter the Author of the Book: "); + author = reader.readLine(); } - + ArrayList matchedBooks = new ArrayList(); - + //Retrieving all the books which matched the user's search query for(int i = 0; i < booksInLibrary.size(); i++) { Book b = booksInLibrary.get(i); - + if (choice.equals("1")) - { + { if (b.getTitle().equals(title)) matchedBooks.add(b); } else if (choice.equals("2")) - { + { if (b.getSubject().equals(subject)) matchedBooks.add(b); } else { if (b.getAuthor().equals(author)) - matchedBooks.add(b); + matchedBooks.add(b); } } - + //Printing all the matched Books if (!matchedBooks.isEmpty()) { System.out.println("\nThese books are found: \n"); - - System.out.println("------------------------------------------------------------------------------"); + + System.out.println("------------------------------------------------------------------------------"); System.out.println("No.\t\tTitle\t\t\tAuthor\t\t\tSubject"); System.out.println("------------------------------------------------------------------------------"); - + for (int i = 0; i < matchedBooks.size(); i++) - { + { System.out.print(i + "-" + "\t\t"); matchedBooks.get(i).printInfo(); System.out.print("\n"); } - + return matchedBooks; } else @@ -358,64 +358,64 @@ else if (choice.equals("2")) return null; } } - - - + + + // View Info of all Books in Library - public void viewAllBooks() + public void viewAllBooks() { if (!booksInLibrary.isEmpty()) - { + { System.out.println("\nBooks are: "); - - System.out.println("------------------------------------------------------------------------------"); + + System.out.println("------------------------------------------------------------------------------"); System.out.println("No.\t\tTitle\t\t\tAuthor\t\t\tSubject"); System.out.println("------------------------------------------------------------------------------"); - + for (int i = 0; i < booksInLibrary.size(); i++) - { + { System.out.print(i + "-" + "\t\t"); booksInLibrary.get(i).printInfo(); System.out.print("\n"); } } else - System.out.println("\nCurrently, Library has no books."); + System.out.println("\nCurrently, Library has no books."); } - + //Computes total fine for all loans of a borrower public double computeFine2(Borrower borrower) { - System.out.println("---------------------------------------------------------------------------------------------------------------------------------------------------------------------"); + System.out.println("---------------------------------------------------------------------------------------------------------------------------------------------------------------------"); System.out.println("No.\t\tBook's Title\t\tBorrower's Name\t\t\tIssued Date\t\t\tReturned Date\t\t\t\tFine(Rs)"); - System.out.println("-------------------------------------------------------------------------------------------------------------------------------------------------------------------"); - - double totalFine = 0; + System.out.println("-------------------------------------------------------------------------------------------------------------------------------------------------------------------"); + + double totalFine = 0; double per_loan_fine = 0; - + for (int i = 0; i < loans.size(); i++) { Loan l = loans.get(i); - + if ((l.getBorrower() == borrower)) { per_loan_fine = l.computeFine1(); - System.out.print(i + "-" + "\t\t" + loans.get(i).getBook().getTitle() + "\t\t\t" + loans.get(i).getBorrower().getName() + "\t\t" + loans.get(i).getIssuedDate() + "\t\t\t" + loans.get(i).getReturnDate() + "\t\t\t\t" + per_loan_fine + "\n"); - + System.out.print(i + "-" + "\t\t" + loans.get(i).getBook().getTitle() + "\t\t\t" + loans.get(i).getBorrower().getName() + "\t\t" + loans.get(i).getIssuedDate() + "\t\t\t" + loans.get(i).getReturnDate() + "\t\t\t\t" + per_loan_fine + "\n"); + totalFine += per_loan_fine; - } + } } - + return totalFine; } - - + + public void createPerson(char x) { Scanner sc = new Scanner(System.in); BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); - + System.out.println("\nEnter Name: "); String n = ""; try { @@ -430,9 +430,9 @@ public void createPerson(char x) } catch (IOException ex) { Logger.getLogger(Library.class.getName()).log(Level.SEVERE, null, ex); } - + int phone = 0; - + try{ System.out.println("Enter Phone Number: "); phone = sc.nextInt(); @@ -441,12 +441,12 @@ public void createPerson(char x) { System.out.println("\nInvalid Input."); } - + //If clerk is to be created if (x == 'c') { double salary = 0; - + try{ System.out.println("Enter Salary: "); salary = sc.nextDouble(); @@ -455,19 +455,19 @@ public void createPerson(char x) { System.out.println("\nInvalid Input."); } - - Clerk c = new Clerk(-1,n,address,phone,salary,-1); + + Clerk c = new Clerk(-1,n,address,phone,salary,-1); addClerk(c); - + System.out.println("\nClerk with name " + n + " created successfully."); System.out.println("\nYour ID is : " + c.getID()); System.out.println("Your Password is : " + c.getPassword()); } - + //If librarian is to be created else if (x == 'l') { - double salary = 0; + double salary = 0; try{ System.out.println("Enter Salary: "); salary = sc.nextDouble(); @@ -476,8 +476,8 @@ else if (x == 'l') { System.out.println("\nInvalid Input."); } - - Librarian l = new Librarian(-1,n,address,phone,salary,-1); + + Librarian l = new Librarian(-1,n,address,phone,salary,-1); if(Librarian.addLibrarian(l)) { System.out.println("\nLibrarian with name " + n + " created successfully."); @@ -490,37 +490,37 @@ else if (x == 'l') else { Borrower b = new Borrower(-1,n,address,phone); - addBorrower(b); + addBorrower(b); System.out.println("\nBorrower with name " + n + " created successfully."); System.out.println("\nYour ID is : " + b.getID()); - System.out.println("Your Password is : " + b.getPassword()); - } + System.out.println("Your Password is : " + b.getPassword()); + } } - - + + public void createBook(String title, String subject, String author) { Book b = new Book(-1,title,subject,author,false); - + addBookinLibrary(b); - + System.out.println("\nBook with Title " + b.getTitle() + " is successfully created."); } - - + + // Called when want an access to Portal public Person login() { Scanner input = new Scanner(System.in); - + int id = 0; String password = ""; - + System.out.println("\nEnter ID: "); - + try{ id = input.nextInt(); } @@ -528,10 +528,10 @@ public Person login() { System.out.println("\nInvalid Input"); } - + System.out.println("Enter Password: "); password = input.next(); - + for (int i = 0; i < persons.size(); i++) { if (persons.get(i).getID() == id && persons.get(i).getPassword().equals(password)) @@ -540,7 +540,7 @@ public Person login() return persons.get(i); } } - + if(librarian!=null) { if (librarian.getID() == id && librarian.getPassword().equals(password)) @@ -549,28 +549,28 @@ public Person login() return librarian; } } - - System.out.println("\nSorry! Wrong ID or Password"); + + System.out.println("\nSorry! Wrong ID or Password"); return null; } - - + + // History when a Book was Issued and was Returned! public void viewHistory() { if (!loans.isEmpty()) - { + { System.out.println("\nIssued Books are: "); - - System.out.println("------------------------------------------------------------------------------------------------------------------------------------------------------"); + + System.out.println("------------------------------------------------------------------------------------------------------------------------------------------------------"); System.out.println("No.\tBook's Title\tBorrower's Name\t Issuer's Name\t\tIssued Date\t\t\tReceiver's Name\t\tReturned Date\t\tFine Paid"); System.out.println("------------------------------------------------------------------------------------------------------------------------------------------------------"); - + for (int i = 0; i < loans.size(); i++) - { + { if(loans.get(i).getIssuer()!=null) System.out.print(i + "-" + "\t" + loans.get(i).getBook().getTitle() + "\t\t\t" + loans.get(i).getBorrower().getName() + "\t\t" + loans.get(i).getIssuer().getName() + "\t " + loans.get(i).getIssuedDate()); - + if (loans.get(i).getReceiver() != null) { System.out.print("\t" + loans.get(i).getReceiver().getName() + "\t\t" + loans.get(i).getReturnDate() +"\t " + loans.get(i).getFineStatus() + "\n"); @@ -580,23 +580,23 @@ public void viewHistory() } } else - System.out.println("\nNo issued books."); + System.out.println("\nNo issued books."); } - - - - - - - - - + + + + + + + + + //---------------------------------------------------------------------------------------// /*--------------------------------IN- COLLABORATION WITH DATA BASE------------------------------------------*/ - - // Making Connection With Database + + // Making Connection With Database public Connection makeConnection() - { + { try { String host = "jdbc:derby://localhost:1527/LMS"; @@ -605,441 +605,441 @@ public Connection makeConnection() Connection con = DriverManager.getConnection( host, uName, uPass ); return con; } - catch ( SQLException err ) + catch ( SQLException err ) { System.out.println( err.getMessage( ) ); return null; - } + } } - - + + // Loading all info in code via Database. public void populateLibrary(Connection con) throws SQLException, IOException - { - Library lib = this; - Statement stmt = con.createStatement( ); - - /* --- Populating Book ----*/ - String SQL = "SELECT * FROM BOOK"; - ResultSet rs = stmt.executeQuery( SQL ); - - if(!rs.next()) - { - System.out.println("\nNo Books Found in Library"); - } - else - { - int maxID = 0; - - do - { - if(rs.getString("TITLE") !=null && rs.getString("AUTHOR")!=null && rs.getString("SUBJECT")!=null && rs.getInt("ID")!=0) - { - String title=rs.getString("TITLE"); - String author=rs.getString("AUTHOR"); - String subject=rs.getString("SUBJECT"); - int id= rs.getInt("ID"); - boolean issue=rs.getBoolean("IS_ISSUED"); - Book b = new Book(id,title,subject,author,issue); - addBookinLibrary(b); - - if (maxID < id) - maxID = id; - } - }while(rs.next()); - - // setting Book Count - Book.setIDCount(maxID); - } - - /* ----Populating Clerks----*/ - - SQL="SELECT ID,PNAME,ADDRESS,PASSWORD,PHONE_NO,SALARY,DESK_NO FROM PERSON INNER JOIN CLERK ON ID=C_ID INNER JOIN STAFF ON S_ID=C_ID"; - - rs=stmt.executeQuery(SQL); - - if(!rs.next()) - { - System.out.println("No clerks Found in Library"); - } - else + { + Library lib = this; + Statement stmt = con.createStatement( ); + + /* --- Populating Book ----*/ + String SQL = "SELECT * FROM BOOK"; + ResultSet rs = stmt.executeQuery( SQL ); + + if(!rs.next()) + { + System.out.println("\nNo Books Found in Library"); + } + else + { + int maxID = 0; + + do { - do + if(rs.getString("TITLE") !=null && rs.getString("AUTHOR")!=null && rs.getString("SUBJECT")!=null && rs.getInt("ID")!=0) { - int id=rs.getInt("ID"); - String cname=rs.getString("PNAME"); - String adrs=rs.getString("ADDRESS"); - int phn=rs.getInt("PHONE_NO"); - double sal=rs.getDouble("SALARY"); - int desk=rs.getInt("DESK_NO"); - Clerk c = new Clerk(id,cname,adrs,phn,sal,desk); - - addClerk(c); + String title=rs.getString("TITLE"); + String author=rs.getString("AUTHOR"); + String subject=rs.getString("SUBJECT"); + int id= rs.getInt("ID"); + boolean issue=rs.getBoolean("IS_ISSUED"); + Book b = new Book(id,title,subject,author,issue); + addBookinLibrary(b); + + if (maxID < id) + maxID = id; } - while(rs.next()); - - } - - /*-----Populating Librarian---*/ - SQL="SELECT ID,PNAME,ADDRESS,PASSWORD,PHONE_NO,SALARY,OFFICE_NO FROM PERSON INNER JOIN LIBRARIAN ON ID=L_ID INNER JOIN STAFF ON S_ID=L_ID"; - - rs=stmt.executeQuery(SQL); - if(!rs.next()) + }while(rs.next()); + + // setting Book Count + Book.setIDCount(maxID); + } + + /* ----Populating Clerks----*/ + + SQL="SELECT ID,PNAME,ADDRESS,PASSWORD,PHONE_NO,SALARY,DESK_NO FROM PERSON INNER JOIN CLERK ON ID=C_ID INNER JOIN STAFF ON S_ID=C_ID"; + + rs=stmt.executeQuery(SQL); + + if(!rs.next()) + { + System.out.println("No clerks Found in Library"); + } + else + { + do { - System.out.println("No Librarian Found in Library"); + int id=rs.getInt("ID"); + String cname=rs.getString("PNAME"); + String adrs=rs.getString("ADDRESS"); + int phn=rs.getInt("PHONE_NO"); + double sal=rs.getDouble("SALARY"); + int desk=rs.getInt("DESK_NO"); + Clerk c = new Clerk(id,cname,adrs,phn,sal,desk); + + addClerk(c); } - else + while(rs.next()); + + } + + /*-----Populating Librarian---*/ + SQL="SELECT ID,PNAME,ADDRESS,PASSWORD,PHONE_NO,SALARY,OFFICE_NO FROM PERSON INNER JOIN LIBRARIAN ON ID=L_ID INNER JOIN STAFF ON S_ID=L_ID"; + + rs=stmt.executeQuery(SQL); + if(!rs.next()) + { + System.out.println("No Librarian Found in Library"); + } + else + { + do { - do - { - int id=rs.getInt("ID"); - String lname=rs.getString("PNAME"); - String adrs=rs.getString("ADDRESS"); - int phn=rs.getInt("PHONE_NO"); - double sal=rs.getDouble("SALARY"); - int off=rs.getInt("OFFICE_NO"); - Librarian l= new Librarian(id,lname,adrs,phn,sal,off); - - Librarian.addLibrarian(l); - - }while(rs.next()); - - } - - /*---Populating Borrowers (partially)!!!!!!--------*/ - - SQL="SELECT ID,PNAME,ADDRESS,PASSWORD,PHONE_NO FROM PERSON INNER JOIN BORROWER ON ID=B_ID"; - - rs=stmt.executeQuery(SQL); - - if(!rs.next()) + int id=rs.getInt("ID"); + String lname=rs.getString("PNAME"); + String adrs=rs.getString("ADDRESS"); + int phn=rs.getInt("PHONE_NO"); + double sal=rs.getDouble("SALARY"); + int off=rs.getInt("OFFICE_NO"); + Librarian l= new Librarian(id,lname,adrs,phn,sal,off); + + Librarian.addLibrarian(l); + + }while(rs.next()); + + } + + /*---Populating Borrowers (partially)!!!!!!--------*/ + + SQL="SELECT ID,PNAME,ADDRESS,PASSWORD,PHONE_NO FROM PERSON INNER JOIN BORROWER ON ID=B_ID"; + + rs=stmt.executeQuery(SQL); + + if(!rs.next()) + { + System.out.println("No Borrower Found in Library"); + } + else + { + do { - System.out.println("No Borrower Found in Library"); - } - else + int id=rs.getInt("ID"); + String name=rs.getString("PNAME"); + String adrs=rs.getString("ADDRESS"); + int phn=rs.getInt("PHONE_NO"); + + Borrower b= new Borrower(id,name,adrs,phn); + addBorrower(b); + + }while(rs.next()); + + } + + /*----Populating Loan----*/ + + SQL="SELECT * FROM LOAN"; + + rs=stmt.executeQuery(SQL); + if(!rs.next()) + { + System.out.println("No Books Issued Yet!"); + } + else + { + do { - do + int borid=rs.getInt("BORROWER"); + int bokid=rs.getInt("BOOK"); + int iid=rs.getInt("ISSUER"); + Integer rid=(Integer)rs.getObject("RECEIVER"); + int rd=0; + Date rdate; + + Date idate=new Date (rs.getTimestamp("ISS_DATE").getTime()); + + if(rid!=null) // if there is a receiver { - int id=rs.getInt("ID"); - String name=rs.getString("PNAME"); - String adrs=rs.getString("ADDRESS"); - int phn=rs.getInt("PHONE_NO"); - - Borrower b= new Borrower(id,name,adrs,phn); - addBorrower(b); - - }while(rs.next()); - - } - - /*----Populating Loan----*/ - - SQL="SELECT * FROM LOAN"; - - rs=stmt.executeQuery(SQL); - if(!rs.next()) - { - System.out.println("No Books Issued Yet!"); - } - else - { - do + rdate=new Date (rs.getTimestamp("RET_DATE").getTime()); + rd=(int)rid; + } + else + { + rdate=null; + } + + boolean fineStatus = rs.getBoolean("FINE_PAID"); + + boolean set=true; + + Borrower bb = null; + + + for(int i=0;i books = getBooks(); - - for(int k=0;k books = getBooks(); + + for(int k=0;k persons = lib.getPersons(); - - for(int i=0;i books = lib.getBooks(); - - for(int i=0;i persons = lib.getPersons(); + + for(int i=0;i books = lib.getBooks(); + + for(int i=0;i books = loans; - - for(int i=0;i persons = lib.getPersons(); - - /* Setting Person ID Count */ - int max=0; - - for(int i=0;i books = loans; + + for(int i=0;i persons = lib.getPersons(); - Person.setIDCount(max); + /* Setting Person ID Count */ + int max=0; + + for(int i=0;i books = lib.getBooks(); - + /*Filling Book's Table*/ for(int i=0;i(); + } + + @Test + @DisplayName("LIB-01: Auto office assignment gives sequential office numbers") + void constructorAutoOffice_assignsSequentialNumbers() { + Librarian l1 = new Librarian(-1, "Alice", "Addr", 123, 1000.0, -1); + Librarian l2 = new Librarian(-1, "Bob", "Addr", 456, 1100.0, -1); + + assertEquals(0, l1.officeNo); + assertEquals(1, l2.officeNo); + } + + @Test + @DisplayName("LIB-02: Explicit office number is kept as provided") + void constructorExplicitOffice_usesProvidedNumber() { + Librarian l = new Librarian(-1, "Clara", "Addr", 789, 1200.0, 15); + + assertEquals(15, l.officeNo); + } + + @Test + @DisplayName("LIB-03: First addLibrarian call sets the library librarian") + void addLibrarian_firstCall_setsLibraryLibrarian() { + Librarian librarian = new Librarian(-1, "Chief", "Addr", 321, 1300.0, -1); + + boolean added = Librarian.addLibrarian(librarian); + + assertTrue(added); + assertSame(librarian, Library.librarian); + assertEquals(1, Library.persons.size()); + assertSame(librarian, Library.persons.get(0)); + } + + @Test + @DisplayName("LIB-04: Second addLibrarian call fails and does not replace existing librarian") + void addLibrarian_secondCall_returnsFalseAndDoesNotReplace() { + Librarian first = new Librarian(-1, "Chief", "Addr", 321, 1300.0, -1); + Librarian second = new Librarian(-1, "Deputy", "Addr", 654, 1400.0, -1); + + Librarian.addLibrarian(first); + boolean addedSecond = Librarian.addLibrarian(second); + + assertFalse(addedSecond); + assertSame(first, Library.librarian); + assertEquals(1, Library.persons.size()); + assertSame(first, Library.persons.get(0)); + } +} diff --git a/Project/src/Tests/LibraryTest.java b/Project/src/Tests/LibraryTest.java new file mode 100644 index 0000000..62d6456 --- /dev/null +++ b/Project/src/Tests/LibraryTest.java @@ -0,0 +1,416 @@ +package Tests; + +import LMS.*; +import org.junit.jupiter.api.*; + +import java.io.*; +import java.lang.reflect.Field; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Date; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Essential Unit Tests for Library.java - IMPROVED VERSION + * SWE 303 Project - Library Management System + * + * Coverage: + * - setName/getLibraryName + * - getLibrarian + * - addClerk + * - searchForBooks (including documented defects + disabled flaky scenario) + * - viewHistory + * + * Notes: + * - Library uses Singleton + static fields (persons, librarian), so tests must reset global state. + * - searchForBooks() uses console I/O and mixes Scanner + BufferedReader (environment-dependent behavior). + */ +class LibraryTest { + + private InputStream originalIn; + private PrintStream originalOut; + private Library lib; + + @BeforeEach + void setUp() throws Exception { + originalIn = System.in; + originalOut = System.out; + + resetLibrarySingletonAndStatics(); + + // Use a fresh instance after reset + lib = Library.getInstance(); + } + + @AfterEach + void tearDown() { + System.setIn(originalIn); + System.setOut(originalOut); + } + + // ============================================================================ + // HELPERS + // ============================================================================ + + private void setConsoleInput(String input) { + System.setIn(new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8))); + } + + private String captureOutput(Runnable action) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + System.setOut(new PrintStream(baos, true, StandardCharsets.UTF_8)); + action.run(); + return baos.toString(StandardCharsets.UTF_8); + } + + /** + * Because Library uses a Singleton + static fields, we reset: + * - Library.obj (singleton instance) + * - Library.persons (static) + * - Library.librarian (static) + * - books + loans lists + * - static counters (Person, Book, Clerk, Librarian) + */ + private void resetLibrarySingletonAndStatics() throws Exception { + // reset singleton instance + Field objField = Library.class.getDeclaredField("obj"); + objField.setAccessible(true); + objField.set(null, null); + + // initialize a new instance so fields exist + Library instance = Library.getInstance(); + + // reset static state + Library.librarian = null; + if (Library.persons != null) { + Library.persons.clear(); + } else { + Library.persons = new ArrayList<>(); + } + + // reset instance collections + instance.getBooks().clear(); + + Field loansField = Library.class.getDeclaredField("loans"); + loansField.setAccessible(true); + ((ArrayList) loansField.get(instance)).clear(); + + // reset counters + Person.setIDCount(0); + Book.setIDCount(0); + Clerk.currentdeskNumber = 0; + Librarian.currentOfficeNumber = 0; + } + + // ============================================================================ + // SUITE 1: setName() and getLibraryName() + // ============================================================================ + + @Test + @DisplayName("TC1.1: setName and getLibraryName - normal case") + void testSetName_normalCase() { + lib.setName("TBU Central Library"); + assertEquals("TBU Central Library", lib.getLibraryName()); + } + + @Test + @DisplayName("TC1.2: getLibraryName initially null") + void testGetLibraryName_initialNull() { + assertNull(lib.getLibraryName()); + } + + @Test + @DisplayName("TC1.3: setName multiple times - last value persists") + void testSetName_multipleTimes() { + lib.setName("First"); + lib.setName("Second"); + lib.setName("Final"); + assertEquals("Final", lib.getLibraryName()); + } + @Test + @DisplayName("TC1.4: setName accepts empty string") + void testSetName_emptyString() { + lib.setName(""); + assertEquals("", lib.getLibraryName()); + } + @Test + @DisplayName("TC1.5: setName preserves whitespace") + void testSetName_whitespaceOnly() { + lib.setName(" "); + assertEquals(" ", lib.getLibraryName()); + } + + + // ============================================================================ + // SUITE 2: getLibrarian() + // ============================================================================ + + @Test + @DisplayName("TC2.1: getLibrarian initially null") + void testGetLibrarian_initialNull() { + assertNull(lib.getLibrarian()); + } + + + // ============================================================================ + // SUITE 3: addClerk() + // ============================================================================ + + @Test + @DisplayName("TC3.1: addClerk adds to persons list") + void testAddClerk_basic() { + Clerk clerk = new Clerk(101, "Alice Johnson", "789 Office Blvd", 55001, 35000.0, 5); + int sizeBefore = lib.getPersons().size(); + + lib.addClerk(clerk); + + assertEquals(sizeBefore + 1, lib.getPersons().size()); + assertTrue(lib.getPersons().contains(clerk)); + } + @Test + @DisplayName("TC3.2: addClerk stores the same object reference") + void testAddClerk_storesSameReference() { + Clerk clerk = new Clerk(102, "Bob Smith", "321 Work St", 55002, 40000.0, 10); + + lib.addClerk(clerk); + + Person added = lib.getPersons().get(lib.getPersons().size() - 1); + assertSame(clerk, added); + } + + + @Test + @DisplayName("TC3.3: addClerk multiple clerks - size increases by 2 and contains both") + void testAddClerk_multiple() { + Clerk clerk1 = new Clerk(201, "Clerk A", "Addr", 60001, 30000.0, 1); + Clerk clerk2 = new Clerk(202, "Clerk B", "Addr", 60002, 30000.0, 2); + + int sizeBefore = lib.getPersons().size(); + + lib.addClerk(clerk1); + lib.addClerk(clerk2); + + assertEquals(sizeBefore + 2, lib.getPersons().size()); + assertTrue(lib.getPersons().contains(clerk1)); + assertTrue(lib.getPersons().contains(clerk2)); + } + + @Test + @DisplayName("TC3.4: addClerk allows null (no validation)") + void testAddClerk_nullAllowed() { + int sizeBefore = lib.getPersons().size(); + + lib.addClerk(null); + + assertEquals(sizeBefore + 1, lib.getPersons().size()); + assertNull(lib.getPersons().get(lib.getPersons().size() - 1)); + } + @Test + @DisplayName("TC3.5: addClerk allows duplicate clerk references") + void testAddClerk_duplicatesAllowed() { + Clerk clerk = new Clerk(333, "Dup Clerk", "Addr", 11111, 30000.0, 1); + int sizeBefore = lib.getPersons().size(); + + lib.addClerk(clerk); + lib.addClerk(clerk); + + assertEquals(sizeBefore + 2, lib.getPersons().size()); + } + + + // ============================================================================ + // SUITE 4: searchForBooks() + // ============================================================================ + + @Test + @DisplayName("TC4.1: searchForBooks by title - exact match") + void testSearchForBooks_titleMatch() throws Exception { + lib.addBookinLibrary(new Book(1, "Clean Code", "SE", "Robert Martin", false)); + + setConsoleInput("1\nClean Code\n"); + + ArrayList result = lib.searchForBooks(); + + assertNotNull(result); + assertEquals(1, result.size()); + assertEquals("Clean Code", result.get(0).getTitle()); + } + + @Test + @DisplayName("TC4.2: searchForBooks by title - no match returns null") + void testSearchForBooks_noMatch() throws Exception { + lib.addBookinLibrary(new Book(1, "Clean Code", "SE", "Author", false)); + + setConsoleInput("1\nNonexistent\n"); + + ArrayList result = lib.searchForBooks(); + + assertNull(result); + } + + @Test + @DisplayName("TC4.3: searchForBooks by author - multiple matches") + void testSearchForBooks_authorMatch() throws Exception { + lib.addBookinLibrary(new Book(1, "Book 1", "Subject", "Martin Fowler", false)); + lib.addBookinLibrary(new Book(2, "Book 2", "Subject", "Martin Fowler", false)); + + setConsoleInput("3\nMartin Fowler\n"); + + ArrayList result = lib.searchForBooks(); + + assertNotNull(result); + assertEquals(2, result.size()); + } + + @Test + @DisplayName("TC4.4: searchForBooks - empty library returns null") + void testSearchForBooks_emptyLibrary() throws Exception { + setConsoleInput("1\nAnything\n"); + + ArrayList result = lib.searchForBooks(); + + assertNull(result); + } + + @Test + @DisplayName("TC4.5: searchForBooks - Observed behavior: case-sensitive exact match") + void testSearchForBooks_caseSensitive_defect() throws Exception { + lib.addBookinLibrary(new Book(1, "Clean Code", "SE", "Author", false)); + + setConsoleInput("1\nclean code\n"); + + ArrayList result = lib.searchForBooks(); + + // DEFECT: user expectations usually are case-insensitive search + assertNull(result, "KNOWN DEFECT: Search is case-sensitive"); + } + + @Test + @DisplayName("TC4.6: searchForBooks - Observed behavior: whitespace not trimmed") + void testSearchForBooks_whitespaceTrim_defect() throws Exception { + lib.addBookinLibrary(new Book(1, "Clean Code", "SE", "Author", false)); + + setConsoleInput("1\n Clean Code \n"); + + ArrayList result = lib.searchForBooks(); + + // DEFECT: strict equals() means leading/trailing spaces cause a miss + assertNull(result, "KNOWN : Search does not trim whitespace"); + } + + @Test + @DisplayName("TC4.7: searchForBooks - issued books are still searchable") + void testSearchForBooks_issuedBooks() throws Exception { + lib.addBookinLibrary(new Book(1, "Book A", "Fiction", "Author", false)); + lib.addBookinLibrary(new Book(2, "Book B", "Fiction", "Author", true)); + + setConsoleInput("2\nFiction\n"); + + ArrayList result = lib.searchForBooks(); + + assertNotNull(result); + assertEquals(2, result.size()); + } + @Test + @DisplayName("TC4.8: searchForBooks by subject - exact match") + void testSearchForBooks_subjectMatch() throws Exception { + lib.addBookinLibrary(new Book(1, "Book A", "AI", "Author1", false)); + lib.addBookinLibrary(new Book(2, "Book B", "SE", "Author2", false)); + + setConsoleInput("2\nAI\n"); + + ArrayList result = lib.searchForBooks(); + + assertNotNull(result); + assertEquals(1, result.size()); + assertEquals("AI", result.get(0).getSubject()); + } + + // ============================================================================ + // SUITE 5: viewHistory() + // ============================================================================ + + @Test + @DisplayName("TC5.1: viewHistory with no loans prints empty message") + void testViewHistory_noLoans() { + String output = captureOutput(lib::viewHistory); + assertTrue(output.contains("No issued books.")); + } + + @Test + @DisplayName("TC5.2: viewHistory with one unreturned loan shows placeholders") + void testViewHistory_oneUnreturnedLoan() { + Borrower borrower = new Borrower(1, "John Doe", "Address", 12345); + Clerk issuer = new Clerk(2, "Jane Clerk", "Address", 67890, 30000.0, 1); + Book book = new Book(3, "Test Book", "Fiction", "Author", false); + + lib.addLoan(new Loan(borrower, book, issuer, null, new Date(), null, false)); + + String output = captureOutput(lib::viewHistory); + + assertTrue(output.contains("Test Book")); + assertTrue(output.contains("John Doe")); + assertTrue(output.contains("--"), "Should show placeholders for receiver/return date/fine paid"); + } + + @Test + @DisplayName("TC5.3: viewHistory with returned loan includes receiver") + void testViewHistory_returnedLoan() { + Borrower borrower = new Borrower(1, "Alice", "Addr", 11111); + Clerk issuer = new Clerk(2, "Issuer", "Addr", 22222, 30000.0, 1); + Clerk receiver = new Clerk(3, "Receiver", "Addr", 33333, 30000.0, 2); + Book book = new Book(4, "Returned Book", "Subject", "Author", false); + + lib.addLoan(new Loan(borrower, book, issuer, receiver, new Date(), new Date(), true)); + + String output = captureOutput(lib::viewHistory); + + assertTrue(output.contains("Returned Book")); + assertTrue(output.contains("Receiver")); + } + + @Test + @DisplayName("TC5.4: viewHistory with multiple loans shows all entries") + void testViewHistory_multipleLoans() { + Borrower b1 = new Borrower(1, "Borrower 1", "Addr", 10001); + Borrower b2 = new Borrower(2, "Borrower 2", "Addr", 10002); + Clerk issuer = new Clerk(3, "Issuer", "Addr", 20001, 30000.0, 1); + + Book book1 = new Book(11, "Book One", "S1", "A1", false); + Book book2 = new Book(12, "Book Two", "S2", "A2", false); + + lib.addLoan(new Loan(b1, book1, issuer, null, new Date(), null, false)); + lib.addLoan(new Loan(b2, book2, issuer, null, new Date(), null, false)); + + String output = captureOutput(lib::viewHistory); + + assertTrue(output.contains("Book One")); + assertTrue(output.contains("Book Two")); + assertTrue(output.contains("Borrower 1")); + assertTrue(output.contains("Borrower 2")); + } + + @Test + @DisplayName("TC5.5: viewHistory - same borrower multiple loans appears multiple times") + void testViewHistory_sameBorrowerMultiple() { + Borrower borrower = new Borrower(1, "John", "Addr", 10001); + Clerk issuer = new Clerk(2, "Issuer", "Addr", 20001, 30000.0, 1); + + Book book1 = new Book(11, "First Book", "S1", "A1", false); + Book book2 = new Book(12, "Second Book", "S2", "A2", false); + + lib.addLoan(new Loan(borrower, book1, issuer, null, new Date(), null, false)); + lib.addLoan(new Loan(borrower, book2, issuer, null, new Date(), null, false)); + + String output = captureOutput(lib::viewHistory); + + assertTrue(output.contains("First Book")); + assertTrue(output.contains("Second Book")); + + // simple check: name appears at least twice + int count = output.split("John", -1).length - 1; + assertTrue(count >= 2, "Borrower name should appear for each loan entry"); + } +} + diff --git a/Project/src/Tests/PersonTest.java b/Project/src/Tests/PersonTest.java new file mode 100644 index 0000000..7b0ed2c --- /dev/null +++ b/Project/src/Tests/PersonTest.java @@ -0,0 +1,104 @@ +package Tests; + +import LMS.Borrower; +import LMS.Person; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Unit tests for Person behavior via a concrete subclass (Borrower), + * because Person is abstract. + * + * Covers: + * - constructor ID logic (auto vs explicit) + * - password initialization based on id + * - getters/setters for name/address/phone + * - static ID counter (setIDCount) + */ +public class PersonTest { + + @BeforeEach + void resetIdCounter() { + Person.setIDCount(0); + } + + @Test + @DisplayName("PER-01: Auto-ID assigns sequential IDs when id = -1") + void constructor_autoId_assignsSequentialIds() { + Borrower p1 = new Borrower(-1, "A", "Addr", 111); + Borrower p2 = new Borrower(-1, "B", "Addr", 222); + + assertEquals(1, p1.getID()); + assertEquals(2, p2.getID()); + } + + @Test + @DisplayName("PER-02: Explicit ID is used when id != -1 (even if counter increments)") + void constructor_explicitId_isUsed() { + Borrower p = new Borrower(99, "Alice", "Addr", 111); + + assertEquals(99, p.getID()); + } + + @Test + @DisplayName("PER-03: Password is always initialized to String.valueOf(id)") + void constructor_passwordEqualsIdString() { + Borrower auto = new Borrower(-1, "Auto", "Addr", 111); + assertEquals(Integer.toString(auto.getID()), auto.getPassword()); + + Borrower explicit = new Borrower(77, "Explicit", "Addr", 222); + assertEquals("77", explicit.getPassword()); + } + + @Test + @DisplayName("PER-04: Name/Address/Phone getters return constructor values") + void getters_returnConstructorValues() { + Borrower p = new Borrower(-1, "Alice", "Street 1", 55555); + + assertEquals("Alice", p.getName()); + assertEquals("Street 1", p.getAddress()); + assertEquals(55555, p.getPhoneNumber()); + } + + @Test + @DisplayName("PER-05: setName updates the name") + void setName_updatesName() { + Borrower p = new Borrower(-1, "Old", "Addr", 111); + p.setName("New"); + + assertEquals("New", p.getName()); + } + + @Test + @DisplayName("PER-06: setAddress updates the address") + void setAddress_updatesAddress() { + Borrower p = new Borrower(-1, "Alice", "Old Addr", 111); + p.setAddress("New Addr"); + + assertEquals("New Addr", p.getAddress()); + } + + @Test + @DisplayName("PER-07: setPhone updates the phone number") + void setPhone_updatesPhone() { + Borrower p = new Borrower(-1, "Alice", "Addr", 111); + p.setPhone(99999); + + assertEquals(99999, p.getPhoneNumber()); + } + + @Test + @DisplayName("PER-08: setIDCount affects the next auto-generated ID") + void setIDCount_affectsNextAutoId() { + Person.setIDCount(10); + + Borrower p = new Borrower(-1, "Alice", "Addr", 111); + + // Person constructor increments currentIdNumber first, then assigns it when id == -1 + assertEquals(11, p.getID()); + assertEquals("11", p.getPassword()); + } +}