diff --git a/robot-framework-python/README.md b/robot-framework-python/README.md new file mode 100644 index 0000000..f8b5fee --- /dev/null +++ b/robot-framework-python/README.md @@ -0,0 +1,34 @@ +# Library Management System + +A comprehensive library management system for testing Robot Framework integration with Testomat.io + +## Installation +```bash +pip install robot-framework-reporter +``` + +## Running Tests +```bash +# Run all tests +robot tests/ + +# Run specific test suite +robot tests/test_book_management.robot +robot tests/test_borrowing.robot + +# Run tests with specific tags +robot --include positive tests/ +robot --include borrow tests/ +robot --include fine tests/ +``` +## Load Tests To Testomat.io +1. Create empty project in Testomat.io +2. Obtain API key from Testomat.io +```bash +robot --listener reporter.listener.ImportListener tests/ +``` + +## Reporting Tests To Testomat.io +```bash +robot --listener reporter.listener.ReportListener tests/ +``` diff --git a/robot-framework-python/src/library_system/__init__.py b/robot-framework-python/src/library_system/__init__.py new file mode 100644 index 0000000..465fa10 --- /dev/null +++ b/robot-framework-python/src/library_system/__init__.py @@ -0,0 +1,21 @@ +from .book import Book +from .member import Member +from .library_system import LibrarySystem +from .exceptions import ( + BookNotAvailableError, + BookNotFoundError, + MemberNotFoundError, + MaxBooksExceededError, + InvalidDateError +) + +__all__ = [ + 'Book', + 'Member', + 'LibrarySystem', + 'BookNotAvailableError', + 'BookNotFoundError', + 'MemberNotFoundError', + 'MaxBooksExceededError', + 'InvalidDateError' +] \ No newline at end of file diff --git a/robot-framework-python/src/library_system/book.py b/robot-framework-python/src/library_system/book.py new file mode 100644 index 0000000..7cd9a30 --- /dev/null +++ b/robot-framework-python/src/library_system/book.py @@ -0,0 +1,50 @@ + +class Book: + """Represents a book in the library.""" + + def __init__(self, isbn, title, author, year, genre, copies=1): + self.isbn = isbn + self.title = title + self.author = author + self.year = year + self.genre = genre + self.total_copies = int(copies) + self.available_copies = int(copies) + self.borrowed_by = [] # List of (member_id, borrow_date) tuples + + def is_available(self): + """Check if book is available for borrowing.""" + return self.available_copies > 0 + + def borrow(self, member_id, date): + """Mark book as borrowed.""" + if not self.is_available(): + return False + self.available_copies -= 1 + self.borrowed_by.append((member_id, date)) + return True + + def return_book(self, member_id): + """Mark book as returned.""" + for i, (mid, _) in enumerate(self.borrowed_by): + if mid == member_id: + self.borrowed_by.pop(i) + self.available_copies += 1 + return True + return False + + def get_info(self): + """Get book information as dictionary.""" + return { + 'isbn': self.isbn, + 'title': self.title, + 'author': self.author, + 'year': self.year, + 'genre': self.genre, + 'total_copies': self.total_copies, + 'available_copies': self.available_copies, + 'borrowed_count': len(self.borrowed_by) + } + + def __str__(self): + return f"{self.title} by {self.author} ({self.year})" diff --git a/robot-framework-python/src/library_system/exceptions.py b/robot-framework-python/src/library_system/exceptions.py new file mode 100644 index 0000000..d011a01 --- /dev/null +++ b/robot-framework-python/src/library_system/exceptions.py @@ -0,0 +1,30 @@ + + +class LibraryException(Exception): + """Base exception for library system.""" + pass + + +class BookNotFoundError(LibraryException): + """Raised when a book is not found.""" + pass + + +class BookNotAvailableError(LibraryException): + """Raised when a book is not available for borrowing.""" + pass + + +class MemberNotFoundError(LibraryException): + """Raised when a member is not found.""" + pass + + +class MaxBooksExceededError(LibraryException): + """Raised when member exceeds maximum borrowed books limit.""" + pass + + +class InvalidDateError(LibraryException): + """Raised when an invalid date is provided.""" + pass diff --git a/robot-framework-python/src/library_system/library_system.py b/robot-framework-python/src/library_system/library_system.py new file mode 100644 index 0000000..3a6052f --- /dev/null +++ b/robot-framework-python/src/library_system/library_system.py @@ -0,0 +1,215 @@ +from .book import Book +from .member import Member +from .exceptions import ( + BookNotAvailableError, + BookNotFoundError, + MemberNotFoundError, + MaxBooksExceededError +) + + +class LibrarySystem: + """Main library management system.""" + + def __init__(self, name="City Library"): + self.name = name + self.books = {} # {isbn: Book} + self.members = {} # {member_id: Member} + self.transaction_history = [] + + # Book Management + def add_book(self, isbn, title, author, year, genre, copies=1): + """Add a new book to the library.""" + if isbn in self.books: + # If book exists, just increase copies + self.books[isbn].total_copies += int(copies) + self.books[isbn].available_copies += int(copies) + else: + self.books[isbn] = Book(isbn, title, author, year, genre, copies) + return f"Added {copies} copy/copies of '{title}'" + + def remove_book(self, isbn): + """Remove a book from the library.""" + if isbn not in self.books: + raise BookNotFoundError(f"Book with ISBN {isbn} not found") + + if self.books[isbn].available_copies < self.books[isbn].total_copies: + return "Cannot remove book: some copies are borrowed" + + del self.books[isbn] + return "Book removed successfully" + + def get_book(self, isbn): + """Get book by ISBN.""" + if isbn not in self.books: + raise BookNotFoundError(f"Book with ISBN {isbn} not found") + return self.books[isbn] + + def search_books_by_title(self, title): + """Search books by title (case-insensitive partial match).""" + results = [] + for book in self.books.values(): + if title.lower() in book.title.lower(): + results.append(book) + return results + + def search_books_by_author(self, author): + """Search books by author (case-insensitive partial match).""" + results = [] + for book in self.books.values(): + if author.lower() in book.author.lower(): + results.append(book) + return results + + def get_books_by_genre(self, genre): + """Get all books of a specific genre.""" + results = [] + for book in self.books.values(): + if book.genre.lower() == genre.lower(): + results.append(book) + return results + + def get_available_books(self): + """Get all available books.""" + return [book for book in self.books.values() if book.is_available()] + + def get_total_books_count(self): + """Get total number of books in library.""" + return sum(book.total_copies for book in self.books.values()) + + def get_available_books_count(self): + """Get number of available books.""" + return sum(book.available_copies for book in self.books.values()) + + # Member Management + def register_member(self, member_id, name, email, membership_type='regular'): + """Register a new member.""" + if member_id in self.members: + return "Member ID already exists" + + self.members[member_id] = Member(member_id, name, email, membership_type) + return f"Member {name} registered successfully" + + def remove_member(self, member_id): + """Remove a member.""" + if member_id not in self.members: + raise MemberNotFoundError(f"Member {member_id} not found") + + if len(self.members[member_id].borrowed_books) > 0: + return "Cannot remove member: has borrowed books" + + del self.members[member_id] + return "Member removed successfully" + + def get_member(self, member_id): + """Get member by ID.""" + if member_id not in self.members: + raise MemberNotFoundError(f"Member {member_id} not found") + return self.members[member_id] + + def suspend_member(self, member_id): + """Suspend a member.""" + member = self.get_member(member_id) + member.suspend() + return f"Member {member_id} suspended" + + def reactivate_member(self, member_id): + """Reactivate a suspended member.""" + member = self.get_member(member_id) + member.reactivate() + return f"Member {member_id} reactivated" + + # Borrowing and Returning + def borrow_book(self, member_id, isbn, date): + """Process book borrowing.""" + member = self.get_member(member_id) + book = self.get_book(isbn) + + if not member.is_active: + raise MaxBooksExceededError("Member account is suspended") + + if not book.is_available(): + raise BookNotAvailableError(f"Book '{book.title}' is not available") + + if not member.can_borrow(): + raise MaxBooksExceededError("Member has reached maximum borrowed books limit") + + book.borrow(member_id, date) + member.borrow_book(isbn, date) + + self.transaction_history.append({ + 'type': 'borrow', + 'member_id': member_id, + 'isbn': isbn, + 'date': date + }) + + return f"Book '{book.title}' borrowed by {member.name}" + + def return_book(self, member_id, isbn, return_date): + """Process book return.""" + member = self.get_member(member_id) + book = self.get_book(isbn) + + if isbn not in member.borrowed_books: + return "This book was not borrowed by this member" + + # Calculate and add fine if overdue + fine = member.calculate_fine(isbn, return_date) + if fine > 0: + member.add_fine(fine) + + book.return_book(member_id) + member.return_book(isbn) + + self.transaction_history.append({ + 'type': 'return', + 'member_id': member_id, + 'isbn': isbn, + 'date': return_date, + 'fine': fine + }) + + return f"Book '{book.title}' returned. Fine: ${fine:.2f}" + + # Statistics + def get_most_borrowed_books(self, limit=5): + """Get most borrowed books.""" + books_with_count = [(book, len(book.borrowed_by)) for book in self.books.values()] + books_with_count.sort(key=lambda x: x[1], reverse=True) + return [book for book, _ in books_with_count[:limit]] + + def get_members_with_fines(self): + """Get all members who have outstanding fines.""" + return [member for member in self.members.values() if member.fine_amount > 0] + + def get_overdue_books(self): + """Get all overdue books.""" + from datetime import datetime, timedelta + overdue = [] + + for member in self.members.values(): + for isbn, borrow_date in member.borrowed_books.items(): + borrow_datetime = datetime.strptime(borrow_date, '%Y-%m-%d') + due_date = borrow_datetime + timedelta(days=Member.LOAN_PERIOD_DAYS) + if datetime.now() > due_date: + overdue.append({ + 'member': member, + 'book': self.books[isbn], + 'due_date': due_date.strftime('%Y-%m-%d') + }) + + return overdue + + def get_statistics(self): + """Get library statistics.""" + return { + 'total_books': len(self.books), + 'total_copies': self.get_total_books_count(), + 'available_copies': self.get_available_books_count(), + 'borrowed_copies': self.get_total_books_count() - self.get_available_books_count(), + 'total_members': len(self.members), + 'active_members': len([m for m in self.members.values() if m.is_active]), + 'members_with_fines': len(self.get_members_with_fines()), + 'total_transactions': len(self.transaction_history) + } diff --git a/robot-framework-python/src/library_system/member.py b/robot-framework-python/src/library_system/member.py new file mode 100644 index 0000000..4e666c6 --- /dev/null +++ b/robot-framework-python/src/library_system/member.py @@ -0,0 +1,91 @@ +from datetime import datetime, timedelta + + +class Member: + """Represents a library member.""" + + MAX_BOOKS = 5 + LOAN_PERIOD_DAYS = 14 + FINE_PER_DAY = 0.50 + + def __init__(self, member_id, name, email, membership_type='regular'): + self.member_id = member_id + self.name = name + self.email = email + self.membership_type = membership_type # 'regular', 'premium', 'student' + self.borrowed_books = {} # {isbn: borrow_date} + self.fine_amount = 0.0 + self.is_active = True + + def can_borrow(self): + """Check if member can borrow more books.""" + max_books = self.MAX_BOOKS + if self.membership_type == 'premium': + max_books = 10 + elif self.membership_type == 'student': + max_books = 3 + + return len(self.borrowed_books) < max_books and self.is_active + + def borrow_book(self, isbn, date): + """Add a borrowed book to member's record.""" + if not self.can_borrow(): + return False + self.borrowed_books[isbn] = date + return True + + def return_book(self, isbn): + """Remove a book from member's borrowed books.""" + if isbn in self.borrowed_books: + del self.borrowed_books[isbn] + return True + return False + + def calculate_fine(self, isbn, return_date): + """Calculate fine for overdue book.""" + if isbn not in self.borrowed_books: + return 0.0 + + borrow_date = datetime.strptime(self.borrowed_books[isbn], '%Y-%m-%d') + return_datetime = datetime.strptime(return_date, '%Y-%m-%d') + due_date = borrow_date + timedelta(days=self.LOAN_PERIOD_DAYS) + + if return_datetime > due_date: + days_overdue = (return_datetime - due_date).days + return days_overdue * self.FINE_PER_DAY + return 0.0 + + def add_fine(self, amount): + """Add fine to member's account.""" + self.fine_amount += float(amount) + + def pay_fine(self, amount): + """Pay off fine amount.""" + amount = float(amount) + if amount > self.fine_amount: + amount = self.fine_amount + self.fine_amount -= amount + return self.fine_amount + + def suspend(self): + """Suspend member account.""" + self.is_active = False + + def reactivate(self): + """Reactivate member account.""" + self.is_active = True + + def get_info(self): + """Get member information as dictionary.""" + return { + 'member_id': self.member_id, + 'name': self.name, + 'email': self.email, + 'membership_type': self.membership_type, + 'borrowed_books_count': len(self.borrowed_books), + 'fine_amount': self.fine_amount, + 'is_active': self.is_active + } + + def __str__(self): + return f"{self.name} ({self.member_id}) - {self.membership_type}" diff --git a/robot-framework-python/tests/test_book_management.robot b/robot-framework-python/tests/test_book_management.robot new file mode 100644 index 0000000..caec256 --- /dev/null +++ b/robot-framework-python/tests/test_book_management.robot @@ -0,0 +1,104 @@ +*** Settings *** +Documentation Tests for book management operations +Library OperatingSystem +Suite Setup Setup Python Path + +*** Variables *** +${ISBN_1984} 978-0451524935 +${ISBN_GATSBY} 978-0743273565 + +*** Test Cases *** +Test Add Single Book + [Documentation] Verify adding a single book to library + [Tags] book add positive + ${library}= Create Library + ${result}= Call Method ${library} add_book ${ISBN_1984} 1984 George Orwell 1949 Fiction 1 + Should Contain ${result} Added 1 copy + ${book}= Call Method ${library} get_book ${ISBN_1984} + Should Be Equal ${book.title} 1984 + +Test Add Multiple Copies + [Documentation] Verify adding multiple copies of a book + [Tags] book add positive + ${library}= Create Library + Call Method ${library} add_book ${ISBN_1984} 1984 George Orwell 1949 Fiction 5 + ${book}= Call Method ${library} get_book ${ISBN_1984} + Should Be Equal As Integers ${book.total_copies} 5 + Should Be Equal As Integers ${book.available_copies} 5 + +Test Add Same Book Increases Copies + [Documentation] Verify adding existing book increases copy count + [Tags] book add positive + ${library}= Create Library + Call Method ${library} add_book ${ISBN_1984} 1984 George Orwell 1949 Fiction 2 + Call Method ${library} add_book ${ISBN_1984} 1984 George Orwell 1949 Fiction 3 + ${book}= Call Method ${library} get_book ${ISBN_1984} + Should Be Equal As Integers ${book.total_copies} 5 + +Test Get Book By ISBN + [Documentation] Verify retrieving book by ISBN + [Tags] book get positive + ${library}= Create Library With Books + ${book}= Call Method ${library} get_book ${ISBN_GATSBY} + Should Be Equal ${book.title} The Great Gatsby + Should Be Equal ${book.author} F. Scott Fitzgerald + +Test Get Nonexistent Book + [Documentation] Verify error when getting nonexistent book + [Tags] book get negative + ${library}= Create Library + Run Keyword And Expect Error *BookNotFoundError* + ... Call Method ${library} get_book 999-9999999999 + +Test Remove Book + [Documentation] Verify removing a book from library + [Tags] book remove positive + ${library}= Create Library With Books + ${result}= Call Method ${library} remove_book ${ISBN_1984} + Should Contain ${result} removed successfully + Run Keyword And Expect Error *BookNotFoundError* + ... Call Method ${library} get_book ${ISBN_1984} + +Test Cannot Remove Borrowed Book + [Documentation] Verify cannot remove book that is borrowed + [Tags] book remove negative + ${library}= Create Library With Books And Members + Call Method ${library} borrow_book M001 ${ISBN_1984} 2024-01-01 + ${result}= Call Method ${library} remove_book ${ISBN_1984} + Should Contain ${result} Cannot remove book + +Test Get Total Books Count + [Documentation] Verify total books count + [Tags] book statistics + ${library}= Create Library With Books + ${count}= Call Method ${library} get_total_books_count + Should Be Equal As Integers ${count} 4 + +Test Get Available Books Count + [Documentation] Verify available books count + [Tags] book statistics + ${library}= Create Library With Books And Members + Call Method ${library} borrow_book M001 ${ISBN_1984} 2024-01-01 + ${count}= Call Method ${library} get_available_books_count + Should Be Equal As Integers ${count} 3 + +*** Keywords *** +Setup Python Path + ${src_path}= Normalize Path ${CURDIR}/../src + Evaluate sys.path.insert(0, r"${src_path}") modules=sys + +Create Library + ${library}= Evaluate library_system.LibrarySystem("Test Library") modules=library_system + RETURN ${library} + +Create Library With Books + ${library}= Create Library + Call Method ${library} add_book ${ISBN_1984} 1984 George Orwell 1949 Fiction 2 + Call Method ${library} add_book ${ISBN_GATSBY} The Great Gatsby F. Scott Fitzgerald 1925 Fiction 2 + RETURN ${library} + +Create Library With Books And Members + ${library}= Create Library With Books + Call Method ${library} register_member M001 John Doe john@example.com regular + Call Method ${library} register_member M002 Jane Smith jane@example.com premium + RETURN ${library} \ No newline at end of file diff --git a/robot-framework-python/tests/test_borrowing.robot b/robot-framework-python/tests/test_borrowing.robot new file mode 100644 index 0000000..737710a --- /dev/null +++ b/robot-framework-python/tests/test_borrowing.robot @@ -0,0 +1,160 @@ +*** Settings *** +Documentation Tests for book borrowing and returning +Library OperatingSystem +Suite Setup Setup Python Path + +*** Variables *** +${ISBN_1984} 978-0451524935 +${ISBN_GATSBY} 978-0743273565 + +*** Test Cases *** +Test Borrow Book Successfully + [Documentation] Verify successful book borrowing + [Tags] borrow positive + ${library}= Setup Library With Books And Members + ${result}= Call Method ${library} borrow_book M001 ${ISBN_1984} 2024-01-01 + Should Contain ${result} borrowed by John Doe + + ${book}= Call Method ${library} get_book ${ISBN_1984} + Should Be Equal As Integers ${book.available_copies} 1 + +Test Borrow Multiple Books + [Documentation] Verify member can borrow multiple books + [Tags] borrow positive + ${library}= Setup Library With Books And Members + Call Method ${library} borrow_book M001 ${ISBN_1984} 2024-01-01 + Call Method ${library} borrow_book M001 ${ISBN_GATSBY} 2024-01-01 + + ${member}= Call Method ${library} get_member M001 + ${borrowed_count}= Evaluate len(${member.borrowed_books}) + Should Be Equal As Integers ${borrowed_count} 2 + +Test Regular Member Book Limit + [Documentation] Verify regular member cannot exceed 5 books + [Tags] borrow limit negative + ${library}= Setup Library With Many Books + Call Method ${library} register_member M001 John Doe john@example.com regular + + # Borrow 5 books (should succeed) + FOR ${i} IN RANGE 5 + Call Method ${library} borrow_book M001 ISBN-${i} 2024-01-01 + END + + # Try to borrow 6th book (should fail) + Run Keyword And Expect Error *MaxBooksExceededError* + ... Call Method ${library} borrow_book M001 ISBN-5 2024-01-01 + +Test Premium Member Book Limit + [Documentation] Verify premium member can borrow up to 10 books + [Tags] borrow limit positive + ${library}= Setup Library With Many Books + Call Method ${library} register_member M001 John Doe john@example.com premium + + # Borrow 10 books (should succeed) + FOR ${i} IN RANGE 10 + Call Method ${library} borrow_book M001 ISBN-${i} 2024-01-01 + END + + ${member}= Call Method ${library} get_member M001 + ${borrowed_count}= Evaluate len(${member.borrowed_books}) + Should Be Equal As Integers ${borrowed_count} 10 + +Test Student Member Book Limit + [Documentation] Verify student member cannot exceed 3 books + [Tags] borrow limit negative + ${library}= Setup Library With Many Books + Call Method ${library} register_member M001 Bob Student bob@example.com student + + # Borrow 3 books (should succeed) + FOR ${i} IN RANGE 3 + Call Method ${library} borrow_book M001 ISBN-${i} 2024-01-01 + END + + # Try to borrow 4th book (should fail) + Run Keyword And Expect Error *MaxBooksExceededError* + ... Call Method ${library} borrow_book M001 ISBN-3 2024-01-01 + +Test Borrow Unavailable Book + [Documentation] Verify cannot borrow unavailable book + [Tags] borrow negative + ${library}= Setup Library With Books And Members + + # First member borrows both copies + Call Method ${library} borrow_book M001 ${ISBN_1984} 2024-01-01 + Call Method ${library} borrow_book M001 ${ISBN_1984} 2024-01-01 + + # Second member tries to borrow (should fail) + Run Keyword And Expect Error *BookNotAvailableError* + ... Call Method ${library} borrow_book M002 ${ISBN_1984} 2024-01-01 + +Test Suspended Member Cannot Borrow + [Documentation] Verify suspended member cannot borrow books + [Tags] borrow suspended negative + ${library}= Setup Library With Books And Members + Call Method ${library} suspend_member M001 + + Run Keyword And Expect Error *MaxBooksExceededError*Member account is suspended + ... Call Method ${library} borrow_book M001 ${ISBN_1984} 2024-01-01 + +Test Return Book Successfully + [Documentation] Verify successful book return + [Tags] return positive + ${library}= Setup Library With Books And Members + Call Method ${library} borrow_book M001 ${ISBN_1984} 2024-01-01 + + ${result}= Call Method ${library} return_book M001 ${ISBN_1984} 2024-01-10 + Should Contain ${result} returned + + ${book}= Call Method ${library} get_book ${ISBN_1984} + Should Be Equal As Integers ${book.available_copies} 2 + +Test Return Book Not Borrowed By Member + [Documentation] Verify error when returning book not borrowed by member + [Tags] return negative + ${library}= Setup Library With Books And Members + + ${result}= Call Method ${library} return_book M001 ${ISBN_1984} 2024-01-10 + Should Contain ${result} not borrowed + +Test Return Book On Time No Fine + [Documentation] Verify no fine for on-time return + [Tags] return fine positive + ${library}= Setup Library With Books And Members + Call Method ${library} borrow_book M001 ${ISBN_1984} 2024-01-01 + + # Return within 14 days (loan period) + ${result}= Call Method ${library} return_book M001 ${ISBN_1984} 2024-01-10 + Should Contain ${result} Fine: $0.00 + +Test Return Overdue Book With Fine + [Documentation] Verify fine is calculated for overdue return + [Tags] return fine overdue + ${library}= Setup Library With Books And Members + Call Method ${library} borrow_book M001 ${ISBN_1984} 2024-01-01 + + # Return after 20 days (6 days overdue) + ${result}= Call Method ${library} return_book M001 ${ISBN_1984} 2024-01-21 + Should Contain ${result} Fine: $3.00 + + ${member}= Call Method ${library} get_member M001 + Should Be Equal As Numbers ${member.fine_amount} 3.0 + +*** Keywords *** +Setup Python Path + ${src_path}= Normalize Path ${CURDIR}/../src + Evaluate sys.path.insert(0, r"${src_path}") modules=sys + +Setup Library With Books And Members + ${library}= Evaluate library_system.LibrarySystem("Test Library") modules=library_system + Call Method ${library} add_book ${ISBN_1984} 1984 George Orwell 1949 Fiction 2 + Call Method ${library} add_book ${ISBN_GATSBY} The Great Gatsby F. Scott Fitzgerald 1925 Fiction 2 + Call Method ${library} register_member M001 John Doe john@example.com regular + Call Method ${library} register_member M002 Jane Smith jane@example.com premium + RETURN ${library} + +Setup Library With Many Books + ${library}= Evaluate library_system.LibrarySystem("Test Library") modules=library_system + FOR ${i} IN RANGE 15 + Call Method ${library} add_book ISBN-${i} Book ${i} Author ${i} 2024 Fiction 1 + END + RETURN ${library} \ No newline at end of file diff --git a/robot-framework-python/tests/test_fines.robot b/robot-framework-python/tests/test_fines.robot new file mode 100644 index 0000000..9ccde18 --- /dev/null +++ b/robot-framework-python/tests/test_fines.robot @@ -0,0 +1,115 @@ +*** Settings *** +Documentation Tests for fine calculation and payment +Library OperatingSystem +Suite Setup Setup Python Path + +*** Variables *** +${ISBN_1984} 978-0451524935 + +*** Test Cases *** +Test No Fine For On Time Return + [Documentation] Verify no fine for returning book on time + [Tags] fine positive + ${library}= Setup Library + Call Method ${library} borrow_book M001 ${ISBN_1984} 2024-01-01 + + ${member}= Call Method ${library} get_member M001 + ${fine}= Call Method ${member} calculate_fine ${ISBN_1984} 2024-01-14 + Should Be Equal As Numbers ${fine} 0.0 + +Test Fine For One Day Overdue + [Documentation] Verify fine for 1 day overdue + [Tags] fine overdue + ${library}= Setup Library + Call Method ${library} borrow_book M001 ${ISBN_1984} 2024-01-01 + + ${member}= Call Method ${library} get_member M001 + ${fine}= Call Method ${member} calculate_fine ${ISBN_1984} 2024-01-16 + Should Be Equal As Numbers ${fine} 0.5 + +Test Fine For Multiple Days Overdue + [Documentation] Verify fine for multiple days overdue + [Tags] fine overdue + ${library}= Setup Library + Call Method ${library} borrow_book M001 ${ISBN_1984} 2024-01-01 + + ${member}= Call Method ${library} get_member M001 + ${fine}= Call Method ${member} calculate_fine ${ISBN_1984} 2024-01-25 + Should Be Equal As Numbers ${fine} 5.0 + +Test Add Fine To Member Account + [Documentation] Verify adding fine to member account + [Tags] fine add + ${library}= Setup Library + ${member}= Call Method ${library} get_member M001 + + Call Method ${member} add_fine 10.50 + Should Be Equal As Numbers ${member.fine_amount} 10.50 + +Test Pay Fine Full Amount + [Documentation] Verify paying full fine amount + [Tags] fine payment + ${library}= Setup Library + ${member}= Call Method ${library} get_member M001 + + Call Method ${member} add_fine 20.00 + ${remaining}= Call Method ${member} pay_fine 20.00 + Should Be Equal As Numbers ${remaining} 0.0 + +Test Pay Fine Partial Amount + [Documentation] Verify paying partial fine amount + [Tags] fine payment + ${library}= Setup Library + ${member}= Call Method ${library} get_member M001 + + Call Method ${member} add_fine 50.00 + ${remaining}= Call Method ${member} pay_fine 30.00 + Should Be Equal As Numbers ${remaining} 20.0 + +Test Pay More Than Fine Amount + [Documentation] Verify paying more than owed adjusts to actual amount + [Tags] fine payment edge-case + ${library}= Setup Library + ${member}= Call Method ${library} get_member M001 + + Call Method ${member} add_fine 10.00 + ${remaining}= Call Method ${member} pay_fine 50.00 + Should Be Equal As Numbers ${remaining} 0.0 + +Test Multiple Fines Accumulate + [Documentation] Verify multiple fines accumulate + [Tags] fine accumulate + ${library}= Setup Library + ${member}= Call Method ${library} get_member M001 + + Call Method ${member} add_fine 5.00 + Call Method ${member} add_fine 3.50 + Call Method ${member} add_fine 2.00 + Should Be Equal As Numbers ${member.fine_amount} 10.50 + +Test Get Members With Fines + [Documentation] Verify getting all members with outstanding fines + [Tags] fine statistics + ${library}= Setup Library + + # Add fines to some members + ${member1}= Call Method ${library} get_member M001 + ${member2}= Call Method ${library} get_member M002 + Call Method ${member1} add_fine 10.00 + Call Method ${member2} add_fine 5.00 + + ${members_with_fines}= Call Method ${library} get_members_with_fines + ${count}= Get Length ${members_with_fines} + Should Be Equal As Integers ${count} 2 + +*** Keywords *** +Setup Python Path + ${src_path}= Normalize Path ${CURDIR}/../src + Evaluate sys.path.insert(0, r"${src_path}") modules=sys + +Setup Library + ${library}= Evaluate library_system.LibrarySystem("Test Library") modules=library_system + Call Method ${library} add_book ${ISBN_1984} 1984 George Orwell 1949 Fiction 2 + Call Method ${library} register_member M001 John Doe john@example.com regular + Call Method ${library} register_member M002 Jane Smith jane@example.com premium + RETURN ${library} \ No newline at end of file diff --git a/robot-framework-python/tests/test_member_management.robot b/robot-framework-python/tests/test_member_management.robot new file mode 100644 index 0000000..40201de --- /dev/null +++ b/robot-framework-python/tests/test_member_management.robot @@ -0,0 +1,109 @@ +*** Settings *** +Documentation Tests for member management operations +Library OperatingSystem +Suite Setup Setup Python Path + +*** Test Cases *** +Test Register New Member + [Documentation] Verify registering a new member + [Tags] member register positive + ${library}= Create Library + ${result}= Call Method ${library} register_member M001 John Doe john@example.com regular + Should Contain ${result} registered successfully + ${member}= Call Method ${library} get_member M001 + Should Be Equal ${member.name} John Doe + +Test Register Member With Different Types + [Documentation] Verify registering members with different membership types + [Tags] member register positive + ${library}= Create Library + Call Method ${library} register_member M001 John Doe john@example.com regular + Call Method ${library} register_member M002 Jane Smith jane@example.com premium + Call Method ${library} register_member M003 Bob Student bob@example.com student + + ${member1}= Call Method ${library} get_member M001 + ${member2}= Call Method ${library} get_member M002 + ${member3}= Call Method ${library} get_member M003 + + Should Be Equal ${member1.membership_type} regular + Should Be Equal ${member2.membership_type} premium + Should Be Equal ${member3.membership_type} student + +Test Register Duplicate Member ID + [Documentation] Verify cannot register duplicate member ID + [Tags] member register negative + ${library}= Create Library + Call Method ${library} register_member M001 John Doe john@example.com regular + ${result}= Call Method ${library} register_member M001 Jane Smith jane@example.com regular + Should Contain ${result} already exists + +Test Get Member By ID + [Documentation] Verify retrieving member by ID + [Tags] member get positive + ${library}= Create Library With Members + ${member}= Call Method ${library} get_member M002 + Should Be Equal ${member.name} Jane Smith + Should Be Equal ${member.email} jane@example.com + +Test Get Nonexistent Member + [Documentation] Verify error when getting nonexistent member + [Tags] member get negative + ${library}= Create Library + Run Keyword And Expect Error *MemberNotFoundError* + ... Call Method ${library} get_member M999 + +Test Remove Member + [Documentation] Verify removing a member + [Tags] member remove positive + ${library}= Create Library With Members + ${result}= Call Method ${library} remove_member M001 + Should Contain ${result} removed successfully + Run Keyword And Expect Error *MemberNotFoundError* + ... Call Method ${library} get_member M001 + +Test Cannot Remove Member With Borrowed Books + [Documentation] Verify cannot remove member who has borrowed books + [Tags] member remove negative + ${library}= Create Library With Books And Members + Call Method ${library} borrow_book M001 978-0451524935 2024-01-01 + ${result}= Call Method ${library} remove_member M001 + Should Contain ${result} has borrowed books + +Test Suspend Member + [Documentation] Verify suspending a member account + [Tags] member suspend + ${library}= Create Library With Members + Call Method ${library} suspend_member M001 + ${member}= Call Method ${library} get_member M001 + Should Not Be True ${member.is_active} + +Test Reactivate Member + [Documentation] Verify reactivating a suspended member + [Tags] member reactivate + ${library}= Create Library With Members + Call Method ${library} suspend_member M001 + Call Method ${library} reactivate_member M001 + ${member}= Call Method ${library} get_member M001 + Should Be True ${member.is_active} + +*** Keywords *** +Setup Python Path + ${src_path}= Normalize Path ${CURDIR}/../src + Evaluate sys.path.insert(0, r"${src_path}") modules=sys + +Create Library + ${library}= Evaluate library_system.LibrarySystem("Test Library") modules=library_system + RETURN ${library} + +Create Library With Members + ${library}= Create Library + Call Method ${library} register_member M001 John Doe john@example.com regular + Call Method ${library} register_member M002 Jane Smith jane@example.com premium + Call Method ${library} register_member M003 Bob Student bob@example.com student + RETURN ${library} + +Create Library With Books And Members + ${library}= Create Library With Members + Call Method ${library} add_book 978-0451524935 1984 George Orwell 1949 Fiction 2 + Call Method ${library} add_book 978-0743273565 The Great Gatsby F. Scott Fitzgerald 1925 Fiction 2 + RETURN ${library} \ No newline at end of file diff --git a/robot-framework-python/tests/test_search_and_filter.robot b/robot-framework-python/tests/test_search_and_filter.robot new file mode 100644 index 0000000..f59724c --- /dev/null +++ b/robot-framework-python/tests/test_search_and_filter.robot @@ -0,0 +1,116 @@ +*** Settings *** +Documentation Tests for search and filter functionality +Library OperatingSystem +Suite Setup Setup Python Path + +*** Test Cases *** +Test Search Books By Title Exact Match + [Documentation] Verify searching books by exact title + [Tags] search title positive + ${library}= Setup Library With Books + ${results}= Call Method ${library} search_books_by_title 1984 + ${count}= Get Length ${results} + Should Be Equal As Integers ${count} 1 + Should Be Equal ${results[0].title} 1984 + +Test Search Books By Title Partial Match + [Documentation] Verify searching books by partial title + [Tags] search title positive + ${library}= Setup Library With Books + ${results}= Call Method ${library} search_books_by_title Great + ${count}= Get Length ${results} + Should Be Equal As Integers ${count} 1 + Should Be Equal ${results[0].title} The Great Gatsby + +Test Search Books By Title Case Insensitive + [Documentation] Verify title search is case insensitive + [Tags] search title positive + ${library}= Setup Library With Books + ${results}= Call Method ${library} search_books_by_title GATSBY + ${count}= Get Length ${results} + Should Be Equal As Integers ${count} 1 + +Test Search Books By Title No Results + [Documentation] Verify search returns empty for no matches + [Tags] search title negative + ${library}= Setup Library With Books + ${results}= Call Method ${library} search_books_by_title Nonexistent + ${count}= Get Length ${results} + Should Be Equal As Integers ${count} 0 + +Test Search Books By Author + [Documentation] Verify searching books by author + [Tags] search author positive + ${library}= Setup Library With Books + ${results}= Call Method ${library} search_books_by_author Orwell + ${count}= Get Length ${results} + Should Be Equal As Integers ${count} 1 + Should Be Equal ${results[0].author} George Orwell + +Test Search Books By Author Partial Match + [Documentation] Verify searching books by partial author name + [Tags] search author positive + ${library}= Setup Library With Books + ${results}= Call Method ${library} search_books_by_author Scott + ${count}= Get Length ${results} + Should Be Equal As Integers ${count} 1 + +Test Get Books By Genre + [Documentation] Verify filtering books by genre + [Tags] filter genre positive + ${library}= Setup Library With Books + ${results}= Call Method ${library} get_books_by_genre Fiction + ${count}= Get Length ${results} + Should Be Equal As Integers ${count} 3 + +Test Get Books By Genre No Results + [Documentation] Verify genre filter returns empty for no matches + [Tags] filter genre negative + ${library}= Setup Library With Books + ${results}= Call Method ${library} get_books_by_genre Science + ${count}= Get Length ${results} + Should Be Equal As Integers ${count} 0 + +Test Get Available Books + [Documentation] Verify getting only available books + [Tags] filter available positive + ${library}= Setup Library With Books And Members + + # Borrow one book + Call Method ${library} borrow_book M001 978-0451524935 2024-01-01 + + ${results}= Call Method ${library} get_available_books + ${count}= Get Length ${results} + # 3 books total, 1 borrowed (but has 2 copies), so still 3 books available + Should Be True ${count} >= 2 + +Test Get Available Books When All Borrowed + [Documentation] Verify available books list when copies are borrowed + [Tags] filter available edge-case + ${library}= Evaluate library_system.LibrarySystem("Test Library") modules=library_system + Call Method ${library} add_book ISBN-001 Test Book Test Author 2024 Fiction 1 + Call Method ${library} register_member M001 John Doe john@example.com regular + + # Borrow the only copy + Call Method ${library} borrow_book M001 ISBN-001 2024-01-01 + + ${results}= Call Method ${library} get_available_books + ${count}= Get Length ${results} + Should Be Equal As Integers ${count} 0 + +*** Keywords *** +Setup Python Path + ${src_path}= Normalize Path ${CURDIR}/../src + Evaluate sys.path.insert(0, r"${src_path}") modules=sys + +Setup Library With Books + ${library}= Evaluate library_system.LibrarySystem("Test Library") modules=library_system + Call Method ${library} add_book 978-0451524935 1984 George Orwell 1949 Fiction 2 + Call Method ${library} add_book 978-0743273565 The Great Gatsby F. Scott Fitzgerald 1925 Fiction 2 + Call Method ${library} add_book 978-0061120084 To Kill a Mockingbird Harper Lee 1960 Fiction 2 + RETURN ${library} + +Setup Library With Books And Members + ${library}= Setup Library With Books + Call Method ${library} register_member M001 John Doe john@example.com regular + RETURN ${library} \ No newline at end of file