using Bsevita.Library.Api.Data.Generated.Entities; using Bsevita.Library.Api.Infrastructure; using Bsevita.Library.Api.Services; using Bsevita.Library.Models.Books; using Bsevita.Library.Models.Loans; using Bsevita.Library.Models.Returns; using Bsevita.Library.Models.Students; namespace Bsevita.Library.Api.Tests; public sealed class LibraryWorkflowTests { [Fact] public async Task LoanAndReturn_UpdatesReportsAndAvailability() { await using var database = new TestDatabase(); await database.InitializeAsync(); var clock = new FixedTimeProvider(new DateTimeOffset(2026, 6, 22, 8, 0, 0, TimeSpan.Zero)); var students = new StudentService(database.Context, clock); var books = new BookService(database.Context, clock); var loans = new LoanService(database.Context, clock); var returns = new ReturnService(database.Context, clock); var reports = new ReportService(database.Context, clock); await students.CreateAsync(new SaveStudentRequest { CardNumber = "S-1", FirstName = "Ada", LastName = "Lovelace", ClassName = "4AHIT" }, CancellationToken.None); await books.CreateAsync(new SaveBookRequest { BookNumber = "B-1", Title = "Clean Architecture", Author = "Robert C. Martin", Subject = "Informatik" }, CancellationToken.None); var verification = await loans.VerifyAsync(new VerifyLoanRequest { CardNumber = "S-1", BookNumber = "B-1" }, CancellationToken.None); Assert.True(verification.CanBorrow); var loan = await loans.CreateAsync(new CreateLoanRequest { CardNumber = "S-1", BookNumber = "B-1" }, CancellationToken.None); Assert.Equal(clock.GetUtcNow().AddDays(14), loan.DueAt); Assert.Single(await reports.GetActiveLoansAsync(CancellationToken.None)); Assert.False((await books.GetByIdAsync(loan.BookId, CancellationToken.None)).IsAvailable); var returned = await returns.ReturnAsync(new ReturnBookRequest { BookNumber = "B-1" }, CancellationToken.None); Assert.Equal(loan.Id, returned.LoanId); Assert.Empty(await reports.GetActiveLoansAsync(CancellationToken.None)); Assert.True((await books.GetByIdAsync(loan.BookId, CancellationToken.None)).IsAvailable); } [Fact] public async Task CreateLoan_UsesCustomDueDate_WhenProvided() { await using var database = new TestDatabase(); await database.InitializeAsync(); var clock = new FixedTimeProvider(new DateTimeOffset(2026, 6, 22, 8, 0, 0, TimeSpan.Zero)); var students = new StudentService(database.Context, clock); var books = new BookService(database.Context, clock); var loans = new LoanService(database.Context, clock); await students.CreateAsync(new SaveStudentRequest { CardNumber = "S-1", FirstName = "Ada", LastName = "Lovelace", ClassName = "4AHIT" }, CancellationToken.None); await books.CreateAsync(new SaveBookRequest { BookNumber = "B-1", Title = "Clean Architecture", Author = "Robert C. Martin", Subject = "Informatik" }, CancellationToken.None); var dueAt = new DateTimeOffset(2026, 7, 20, 0, 0, 0, TimeSpan.Zero); var loan = await loans.CreateAsync(new CreateLoanRequest { CardNumber = "S-1", BookNumber = "B-1", DueAt = dueAt }, CancellationToken.None); Assert.Equal(dueAt, loan.DueAt); } [Fact] public async Task DuplicateCardNumber_IsRejected() { await using var database = new TestDatabase(); await database.InitializeAsync(); var service = new StudentService(database.Context, new FixedTimeProvider(DateTimeOffset.UtcNow)); var request = new SaveStudentRequest { CardNumber = "same", FirstName = "A", LastName = "B", ClassName = "1A" }; await service.CreateAsync(request, CancellationToken.None); await Assert.ThrowsAsync( () => service.CreateAsync(new SaveStudentRequest { CardNumber = "SAME", FirstName = "C", LastName = "D", ClassName = "1B" }, CancellationToken.None)); } [Fact] public async Task Statistics_CountsAvailableActiveBooks() { await using var database = new TestDatabase(); await database.InitializeAsync(); var now = new DateTimeOffset(2026, 6, 22, 8, 0, 0, TimeSpan.Zero); var student = new Student { StudentId = Guid.NewGuid(), CardNumber = "S-1", FirstName = "Ada", LastName = "Lovelace", ClassName = "4AHIT", IsActive = true, CreatedAt = now, UpdatedAt = now }; var activeBook = new Book { BookId = Guid.NewGuid(), BookNumber = "B-1", Title = "Active", Author = "A", Subject = "S", IsActive = true, CreatedAt = now, UpdatedAt = now }; var inactiveBook = new Book { BookId = Guid.NewGuid(), BookNumber = "B-2", Title = "Inactive", Author = "A", Subject = "S", IsActive = false, CreatedAt = now, UpdatedAt = now }; database.Context.AddRange(student, activeBook, inactiveBook, new Loan { LoanId = Guid.NewGuid(), StudentId = student.StudentId, BookId = inactiveBook.BookId, LoanedAt = now.AddDays(-1), DueAt = now.AddDays(13) }); await database.Context.SaveChangesAsync(); var statistics = await new ReportService(database.Context, new FixedTimeProvider(now)).GetStatisticsAsync(CancellationToken.None); Assert.Equal(1, statistics.TotalBooks); Assert.Equal(1, statistics.AvailableBooks); Assert.Equal(1, statistics.ActiveLoans); } [Fact] public async Task BookList_CanFilterAvailabilityAndLimitRows() { await using var database = new TestDatabase(); await database.InitializeAsync(); var clock = new FixedTimeProvider(new DateTimeOffset(2026, 6, 22, 8, 0, 0, TimeSpan.Zero)); var students = new StudentService(database.Context, clock); var books = new BookService(database.Context, clock); var loans = new LoanService(database.Context, clock); await students.CreateAsync(new SaveStudentRequest { CardNumber = "S-1", FirstName = "Ada", LastName = "Lovelace", ClassName = "4AHIT" }, CancellationToken.None); await books.CreateAsync(new SaveBookRequest { BookNumber = "B-1", Title = "Available", Author = "A", Subject = "S" }, CancellationToken.None); await books.CreateAsync(new SaveBookRequest { BookNumber = "B-2", Title = "Borrowed", Author = "A", Subject = "S" }, CancellationToken.None); await loans.CreateAsync(new CreateLoanRequest { CardNumber = "S-1", BookNumber = "B-2" }, CancellationToken.None); var available = await books.GetAllAsync(availableOnly: true, skip: 0, take: 1, CancellationToken.None); var borrowed = await books.GetAllAsync(availableOnly: false, skip: 0, take: 10, CancellationToken.None); Assert.Single(available); Assert.Equal("B-1", available[0].BookNumber); Assert.Collection(borrowed, book => Assert.Equal("B-2", book.BookNumber)); } [Fact] public async Task Reports_ReturnsBundledReportData() { await using var database = new TestDatabase(); await database.InitializeAsync(); var clock = new FixedTimeProvider(new DateTimeOffset(2026, 6, 22, 8, 0, 0, TimeSpan.Zero)); var students = new StudentService(database.Context, clock); var books = new BookService(database.Context, clock); var loans = new LoanService(database.Context, clock); await students.CreateAsync(new SaveStudentRequest { CardNumber = "S-1", FirstName = "Ada", LastName = "Lovelace", ClassName = "4AHIT" }, CancellationToken.None); await books.CreateAsync(new SaveBookRequest { BookNumber = "B-1", Title = "Overdue", Author = "A", Subject = "S" }, CancellationToken.None); await loans.CreateAsync(new CreateLoanRequest { CardNumber = "S-1", BookNumber = "B-1", DueAt = clock.GetUtcNow().AddDays(1) }, CancellationToken.None); var reports = await new ReportService(database.Context, new FixedTimeProvider(clock.GetUtcNow().AddDays(2))).GetReportsAsync(CancellationToken.None); Assert.Single(reports.ActiveLoans); Assert.Single(reports.OverdueLoans); Assert.Single(reports.ActiveStudents); Assert.Equal(1, reports.Statistics.ActiveLoans); Assert.Equal(0, reports.Statistics.AvailableBooks); } [Fact] public async Task Reports_LimitsListsButKeepsFullStatistics() { await using var database = new TestDatabase(); await database.InitializeAsync(); var now = new DateTimeOffset(2026, 6, 22, 8, 0, 0, TimeSpan.Zero); var student = new Student { StudentId = Guid.NewGuid(), CardNumber = "S-1", FirstName = "Ada", LastName = "Lovelace", ClassName = "4AHIT", IsActive = true, CreatedAt = now, UpdatedAt = now }; database.Context.Students.Add(student); for (var i = 0; i < 101; i++) { var book = new Book { BookId = Guid.NewGuid(), BookNumber = $"B-{i}", Title = $"Book {i}", Author = "A", Subject = "S", IsActive = true, CreatedAt = now, UpdatedAt = now }; database.Context.Add(book); database.Context.Loans.Add(new Loan { LoanId = Guid.NewGuid(), StudentId = student.StudentId, BookId = book.BookId, LoanedAt = now.AddDays(-1), DueAt = now.AddDays(13) }); } await database.Context.SaveChangesAsync(); var reports = await new ReportService(database.Context, new FixedTimeProvider(now)).GetReportsAsync(CancellationToken.None); Assert.Equal(100, reports.ActiveLoans.Count); Assert.Equal(101, reports.Statistics.ActiveLoans); } [Fact] public async Task Search_FindsPartialMatches() { await using var database = new TestDatabase(); await database.InitializeAsync(); var clock = new FixedTimeProvider(new DateTimeOffset(2026, 6, 22, 8, 0, 0, TimeSpan.Zero)); var students = new StudentService(database.Context, clock); var books = new BookService(database.Context, clock); await students.CreateAsync(new SaveStudentRequest { CardNumber = "S-1", FirstName = "Ada", LastName = "Lovelace", ClassName = "4AHIT" }, CancellationToken.None); await books.CreateAsync(new SaveBookRequest { BookNumber = "B-1", Title = "Clean Architecture", Author = "Robert Martin", Subject = "Informatik" }, CancellationToken.None); Assert.Single(await students.SearchAsync("love", CancellationToken.None)); Assert.Single(await books.SearchAsync(title: "arch", author: null, subject: null, CancellationToken.None)); } [Fact] public async Task Loan_ThrowsNotFound_WhenStudentOrBookIsMissing() { await using var database = new TestDatabase(); await database.InitializeAsync(); var clock = NewClock(); var students = new StudentService(database.Context, clock); var books = new BookService(database.Context, clock); var loans = new LoanService(database.Context, clock); await Assert.ThrowsAsync( () => loans.VerifyAsync(new VerifyLoanRequest { CardNumber = "missing", BookNumber = "missing" }, CancellationToken.None)); await students.CreateAsync(Student("S-1"), CancellationToken.None); await Assert.ThrowsAsync( () => loans.CreateAsync(new CreateLoanRequest { CardNumber = "S-1", BookNumber = "missing" }, CancellationToken.None)); await books.CreateAsync(Book("B-1"), CancellationToken.None); await Assert.ThrowsAsync( () => loans.CreateAsync(new CreateLoanRequest { CardNumber = "missing", BookNumber = "B-1" }, CancellationToken.None)); } [Fact] public async Task Loan_ThrowsConflict_WhenBookIsAlreadyBorrowed() { await using var database = new TestDatabase(); await database.InitializeAsync(); var clock = NewClock(); var students = new StudentService(database.Context, clock); var books = new BookService(database.Context, clock); var loans = new LoanService(database.Context, clock); await students.CreateAsync(Student("S-1"), CancellationToken.None); await students.CreateAsync(Student("S-2"), CancellationToken.None); await books.CreateAsync(Book("B-1"), CancellationToken.None); await loans.CreateAsync(new CreateLoanRequest { CardNumber = "S-1", BookNumber = "B-1" }, CancellationToken.None); await Assert.ThrowsAsync( () => loans.VerifyAsync(new VerifyLoanRequest { CardNumber = "S-2", BookNumber = "B-1" }, CancellationToken.None)); await Assert.ThrowsAsync( () => loans.CreateAsync(new CreateLoanRequest { CardNumber = "S-2", BookNumber = "B-1" }, CancellationToken.None)); } [Fact] public async Task Loan_ThrowsValidation_WhenDueDateIsInPast() { await using var database = new TestDatabase(); await database.InitializeAsync(); var clock = NewClock(); var students = new StudentService(database.Context, clock); var books = new BookService(database.Context, clock); var loans = new LoanService(database.Context, clock); await students.CreateAsync(Student("S-1"), CancellationToken.None); await books.CreateAsync(Book("B-1"), CancellationToken.None); await Assert.ThrowsAsync( () => loans.CreateAsync(new CreateLoanRequest { CardNumber = "S-1", BookNumber = "B-1", DueAt = clock.GetUtcNow().AddSeconds(-1) }, CancellationToken.None)); } [Fact] public async Task Return_ThrowsNotFound_WhenNoActiveLoanExists() { await using var database = new TestDatabase(); await database.InitializeAsync(); var returns = new ReturnService(database.Context, NewClock()); await Assert.ThrowsAsync( () => returns.VerifyAsync("missing", CancellationToken.None)); await Assert.ThrowsAsync( () => returns.ReturnAsync(new ReturnBookRequest { BookNumber = "missing" }, CancellationToken.None)); } [Fact] public async Task Return_ReportsOverdueDays() { await using var database = new TestDatabase(); await database.InitializeAsync(); var start = new FixedTimeProvider(new DateTimeOffset(2026, 6, 22, 8, 0, 0, TimeSpan.Zero)); var overdueClock = new FixedTimeProvider(start.GetUtcNow().AddDays(3)); var students = new StudentService(database.Context, start); var books = new BookService(database.Context, start); var loans = new LoanService(database.Context, start); await students.CreateAsync(Student("S-1"), CancellationToken.None); await books.CreateAsync(Book("B-1"), CancellationToken.None); await loans.CreateAsync(new CreateLoanRequest { CardNumber = "S-1", BookNumber = "B-1", DueAt = start.GetUtcNow().AddDays(1) }, CancellationToken.None); var returns = new ReturnService(database.Context, overdueClock); var verification = await returns.VerifyAsync("B-1", CancellationToken.None); var result = await returns.ReturnAsync(new ReturnBookRequest { BookNumber = "B-1" }, CancellationToken.None); Assert.True(verification.IsOverdue); Assert.Equal(2, verification.DaysOverdue); Assert.True(result.WasOverdue); Assert.Equal(2, result.DaysOverdue); } [Fact] public async Task Delete_ThrowsConflict_WhenActiveLoanExists() { await using var database = new TestDatabase(); await database.InitializeAsync(); var clock = NewClock(); var students = new StudentService(database.Context, clock); var books = new BookService(database.Context, clock); var loans = new LoanService(database.Context, clock); var student = await students.CreateAsync(Student("S-1"), CancellationToken.None); var book = await books.CreateAsync(Book("B-1"), CancellationToken.None); await loans.CreateAsync(new CreateLoanRequest { CardNumber = "S-1", BookNumber = "B-1" }, CancellationToken.None); await Assert.ThrowsAsync(() => students.DeleteAsync(student.Id, CancellationToken.None)); await Assert.ThrowsAsync(() => books.DeleteAsync(book.Id, CancellationToken.None)); } [Fact] public async Task Delete_DeactivatesStudentAndBook() { await using var database = new TestDatabase(); await database.InitializeAsync(); var clock = NewClock(); var students = new StudentService(database.Context, clock); var books = new BookService(database.Context, clock); var student = await students.CreateAsync(Student("S-1"), CancellationToken.None); var book = await books.CreateAsync(Book("B-1"), CancellationToken.None); await students.DeleteAsync(student.Id, CancellationToken.None); await books.DeleteAsync(book.Id, CancellationToken.None); Assert.Empty(await students.GetAllAsync(0, 10, CancellationToken.None)); Assert.Empty(await books.GetAllAsync(null, 0, 10, CancellationToken.None)); await Assert.ThrowsAsync(() => students.GetByIdAsync(student.Id, CancellationToken.None)); await Assert.ThrowsAsync(() => books.GetByIdAsync(book.Id, CancellationToken.None)); } [Fact] public async Task BookIdentifiers_AreUnique() { await using var database = new TestDatabase(); await database.InitializeAsync(); var books = new BookService(database.Context, NewClock()); await books.CreateAsync(Book("B-1", isbn: "978-1"), CancellationToken.None); await Assert.ThrowsAsync( () => books.CreateAsync(Book("b-1"), CancellationToken.None)); await Assert.ThrowsAsync( () => books.CreateAsync(Book("B-2", isbn: "978-1"), CancellationToken.None)); } [Fact] public async Task Update_ThrowsConflict_WhenIdentifiersAlreadyExist() { await using var database = new TestDatabase(); await database.InitializeAsync(); var clock = NewClock(); var students = new StudentService(database.Context, clock); var books = new BookService(database.Context, clock); await students.CreateAsync(Student("S-1"), CancellationToken.None); var secondStudent = await students.CreateAsync(Student("S-2"), CancellationToken.None); await books.CreateAsync(Book("B-1", isbn: "978-1"), CancellationToken.None); var secondBook = await books.CreateAsync(Book("B-2", isbn: "978-2"), CancellationToken.None); await Assert.ThrowsAsync( () => students.UpdateAsync(secondStudent.Id, Student("S-1"), CancellationToken.None)); await Assert.ThrowsAsync( () => books.UpdateAsync(secondBook.Id, Book("B-1", isbn: "978-2"), CancellationToken.None)); await Assert.ThrowsAsync( () => books.UpdateAsync(secondBook.Id, Book("B-2", isbn: "978-1"), CancellationToken.None)); } [Fact] public async Task Search_ThrowsValidation_WhenSearchInputIsMissing() { await using var database = new TestDatabase(); await database.InitializeAsync(); var clock = NewClock(); var students = new StudentService(database.Context, clock); var books = new BookService(database.Context, clock); await Assert.ThrowsAsync( () => students.SearchAsync(" ", CancellationToken.None)); await Assert.ThrowsAsync( () => books.SearchAsync(null, null, null, CancellationToken.None)); } [Fact] public async Task Reads_ThrowNotFound_WhenResourcesDoNotExist() { await using var database = new TestDatabase(); await database.InitializeAsync(); var clock = NewClock(); var students = new StudentService(database.Context, clock); var books = new BookService(database.Context, clock); await Assert.ThrowsAsync( () => students.GetByIdAsync(Guid.NewGuid(), CancellationToken.None)); await Assert.ThrowsAsync( () => students.GetByCardAsync("missing", CancellationToken.None)); await Assert.ThrowsAsync( () => books.GetByIdAsync(Guid.NewGuid(), CancellationToken.None)); await Assert.ThrowsAsync( () => books.GetByNumberAsync("missing", CancellationToken.None)); } [Fact] public async Task Paging_ClampsInvalidSkipAndTake() { await using var database = new TestDatabase(); await database.InitializeAsync(); var clock = NewClock(); var students = new StudentService(database.Context, clock); var books = new BookService(database.Context, clock); await students.CreateAsync(Student("S-1"), CancellationToken.None); await books.CreateAsync(Book("B-1"), CancellationToken.None); Assert.Single(await students.GetAllAsync(-10, 0, CancellationToken.None)); Assert.Single(await books.GetAllAsync(null, -10, 0, CancellationToken.None)); } [Fact] public async Task Update_ChangesStudentAndBookValues() { await using var database = new TestDatabase(); await database.InitializeAsync(); var clock = NewClock(); var students = new StudentService(database.Context, clock); var books = new BookService(database.Context, clock); var student = await students.CreateAsync(Student("S-1"), CancellationToken.None); var book = await books.CreateAsync(Book("B-1"), CancellationToken.None); var updatedStudent = await students.UpdateAsync(student.Id, new SaveStudentRequest { CardNumber = "S-2", FirstName = "Grace", LastName = "Hopper", ClassName = "5BHIT", Email = "grace@example.test" }, CancellationToken.None); var updatedBook = await books.UpdateAsync(book.Id, new SaveBookRequest { BookNumber = "B-2", Isbn = "978-2", Title = "Compiler Design", Author = "Grace Hopper", Subject = "Informatik", Publisher = "ACM", PublicationYear = 1952 }, CancellationToken.None); Assert.Equal("Grace Hopper", updatedStudent.FullName); Assert.Equal("S-2", updatedStudent.CardNumber); Assert.Equal("grace@example.test", updatedStudent.Email); Assert.Equal("Compiler Design", updatedBook.Title); Assert.Equal("978-2", updatedBook.Isbn); Assert.Equal(1952, updatedBook.PublicationYear); } [Fact] public async Task Search_CoversStudentFullNameAndBookAuthorAndSubject() { await using var database = new TestDatabase(); await database.InitializeAsync(); var clock = NewClock(); var students = new StudentService(database.Context, clock); var books = new BookService(database.Context, clock); await students.CreateAsync(new SaveStudentRequest { CardNumber = "S-1", FirstName = "Ada", LastName = "Lovelace", ClassName = "4AHIT" }, CancellationToken.None); await books.CreateAsync(new SaveBookRequest { BookNumber = "B-1", Title = "Clean Architecture", Author = "Robert Martin", Subject = "Software Engineering" }, CancellationToken.None); Assert.Single(await students.SearchAsync("ada love", CancellationToken.None)); Assert.Single(await books.SearchAsync(title: null, author: "martin", subject: null, CancellationToken.None)); Assert.Single(await books.SearchAsync(title: null, author: null, subject: "engineering", CancellationToken.None)); Assert.Empty(await books.SearchAsync(title: "missing", author: null, subject: null, CancellationToken.None)); } [Fact] public async Task Lists_AreSortedAndPaged() { await using var database = new TestDatabase(); await database.InitializeAsync(); var clock = NewClock(); var students = new StudentService(database.Context, clock); var books = new BookService(database.Context, clock); var loans = new LoanService(database.Context, clock); var reports = new ReportService(database.Context, clock); await students.CreateAsync(new SaveStudentRequest { CardNumber = "S-1", FirstName = "B", LastName = "Beta", ClassName = "1A" }, CancellationToken.None); await students.CreateAsync(new SaveStudentRequest { CardNumber = "S-2", FirstName = "A", LastName = "Alpha", ClassName = "1A" }, CancellationToken.None); await books.CreateAsync(new SaveBookRequest { BookNumber = "B-1", Title = "Zulu", Author = "A", Subject = "S" }, CancellationToken.None); await books.CreateAsync(new SaveBookRequest { BookNumber = "B-2", Title = "Alpha", Author = "A", Subject = "S" }, CancellationToken.None); await loans.CreateAsync(new CreateLoanRequest { CardNumber = "S-1", BookNumber = "B-1", DueAt = clock.GetUtcNow().AddDays(5) }, CancellationToken.None); await loans.CreateAsync(new CreateLoanRequest { CardNumber = "S-2", BookNumber = "B-2", DueAt = clock.GetUtcNow().AddDays(1) }, CancellationToken.None); var orderedStudents = await students.GetAllAsync(0, 10, CancellationToken.None); var pagedBooks = await books.GetAllAsync(null, 1, 1, CancellationToken.None); var orderedLoans = await reports.GetActiveLoansAsync(0, 10, CancellationToken.None); Assert.Equal(["Alpha", "Beta"], orderedStudents.Select(student => student.LastName).ToArray()); Assert.Equal("Zulu", pagedBooks.Single().Title); Assert.Equal(["Alpha", "Zulu"], orderedLoans.Select(loan => loan.BookTitle).ToArray()); } [Fact] public async Task Return_ThrowsNotFound_WhenBookIsReturnedTwice() { await using var database = new TestDatabase(); await database.InitializeAsync(); var clock = NewClock(); var students = new StudentService(database.Context, clock); var books = new BookService(database.Context, clock); var loans = new LoanService(database.Context, clock); var returns = new ReturnService(database.Context, clock); await students.CreateAsync(Student("S-1"), CancellationToken.None); await books.CreateAsync(Book("B-1"), CancellationToken.None); await loans.CreateAsync(new CreateLoanRequest { CardNumber = "S-1", BookNumber = "B-1" }, CancellationToken.None); await returns.ReturnAsync(new ReturnBookRequest { BookNumber = "B-1" }, CancellationToken.None); await Assert.ThrowsAsync( () => returns.ReturnAsync(new ReturnBookRequest { BookNumber = "B-1" }, CancellationToken.None)); } [Fact] public async Task SoftDeletedResources_AreHiddenFromSearchAndLookup() { await using var database = new TestDatabase(); await database.InitializeAsync(); var clock = NewClock(); var students = new StudentService(database.Context, clock); var books = new BookService(database.Context, clock); var student = await students.CreateAsync(Student("S-1"), CancellationToken.None); var book = await books.CreateAsync(Book("B-1"), CancellationToken.None); await students.DeleteAsync(student.Id, CancellationToken.None); await books.DeleteAsync(book.Id, CancellationToken.None); Assert.Empty(await students.SearchAsync("Ada", CancellationToken.None)); Assert.Empty(await books.SearchAsync(title: "Book", author: null, subject: null, CancellationToken.None)); await Assert.ThrowsAsync(() => students.GetByCardAsync("S-1", CancellationToken.None)); await Assert.ThrowsAsync(() => books.GetByNumberAsync("B-1", CancellationToken.None)); } private static FixedTimeProvider NewClock() => new(new DateTimeOffset(2026, 6, 22, 8, 0, 0, TimeSpan.Zero)); private static SaveStudentRequest Student(string cardNumber) => new() { CardNumber = cardNumber, FirstName = "Ada", LastName = "Lovelace", ClassName = "4AHIT" }; private static SaveBookRequest Book(string bookNumber, string? isbn = null) => new() { BookNumber = bookNumber, Isbn = isbn, Title = $"Book {bookNumber}", Author = "Author", Subject = "Subject" }; }