Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@ public enum CommonActions
SearchPatrons = 4,
RenewPatronMembership = 8,
ReturnLoanedBook = 16,
ExtendLoanedBook = 32
ExtendLoanedBook = 32,
SearchBooks = 64
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using Library.ApplicationCore;
using Library.ApplicationCore.Entities;
using Library.ApplicationCore.Enums;
using Library.Infrastructure.Data;
using Library.Console;

public class ConsoleApp
Expand All @@ -16,13 +17,15 @@ public class ConsoleApp
ILoanRepository _loanRepository;
ILoanService _loanService;
IPatronService _patronService;
JsonData _jsonData;

public ConsoleApp(ILoanService loanService, IPatronService patronService, IPatronRepository patronRepository, ILoanRepository loanRepository)
public ConsoleApp(ILoanService loanService, IPatronService patronService, IPatronRepository patronRepository, ILoanRepository loanRepository, JsonData jsonData)
{
_patronRepository = patronRepository;
_loanRepository = loanRepository;
_loanService = loanService;
_patronService = patronService;
_jsonData = jsonData;
}

public async Task Run()
Expand Down Expand Up @@ -136,6 +139,7 @@ static CommonActions ReadInputOptions(CommonActions options, out int optionNumbe
{
"q" when options.HasFlag(CommonActions.Quit) => CommonActions.Quit,
"s" when options.HasFlag(CommonActions.SearchPatrons) => CommonActions.SearchPatrons,
"b" when options.HasFlag(CommonActions.SearchBooks) => CommonActions.SearchBooks,
"m" when options.HasFlag(CommonActions.RenewPatronMembership) => CommonActions.RenewPatronMembership,
"e" when options.HasFlag(CommonActions.ExtendLoanedBook) => CommonActions.ExtendLoanedBook,
"r" when options.HasFlag(CommonActions.ReturnLoanedBook) => CommonActions.ReturnLoanedBook,
Expand Down Expand Up @@ -170,6 +174,10 @@ static void WriteInputOptions(CommonActions options)
{
Console.WriteLine(" - \"s\" for new search");
}
if (options.HasFlag(CommonActions.SearchBooks))
{
Console.WriteLine(" - \"b\" to check for book availability");
}
if (options.HasFlag(CommonActions.Quit))
{
Console.WriteLine(" - \"q\" to quit");
Expand All @@ -193,7 +201,7 @@ async Task<ConsoleState> PatronDetails()
loanNumber++;
}

CommonActions options = CommonActions.SearchPatrons | CommonActions.Quit | CommonActions.Select | CommonActions.RenewPatronMembership;
CommonActions options = CommonActions.SearchPatrons | CommonActions.SearchBooks | CommonActions.Quit | CommonActions.Select | CommonActions.RenewPatronMembership;
CommonActions action = ReadInputOptions(options, out int selectedLoanNumber);
if (action == CommonActions.Select)
{
Expand Down Expand Up @@ -225,10 +233,53 @@ async Task<ConsoleState> PatronDetails()
selectedPatronDetails = (await _patronRepository.GetPatron(selectedPatronDetails.Id))!;
return ConsoleState.PatronDetails;
}
else if (action == CommonActions.SearchBooks)
{
return await SearchBooks();
}

throw new InvalidOperationException("An input option is not handled.");
}

async Task<ConsoleState> SearchBooks()
{
string? bookTitle = null;
while (string.IsNullOrWhiteSpace(bookTitle))
{
Console.Write("Enter a book title to search for: ");
bookTitle = Console.ReadLine();
}

await _jsonData.EnsureDataLoaded();

var book = _jsonData.Books!.FirstOrDefault(b => string.Equals(b.Title, bookTitle, StringComparison.OrdinalIgnoreCase));
if (book == null)
{
Console.WriteLine($"No book found with the title \"{bookTitle}\".");
return ConsoleState.PatronDetails;
}

var bookItem = _jsonData.BookItems!.FirstOrDefault(bi => bi.BookId == book.Id);
if (bookItem == null)
{
Console.WriteLine($"No book item found for the title \"{book.Title}\".");
return ConsoleState.PatronDetails;
}

var loan = _jsonData.Loans!.FirstOrDefault(l => l.BookItemId == bookItem.Id && l.ReturnDate == null);
if (loan == null)
{
Console.WriteLine($"\"{book.Title}\" is available for loan.");
}
else
{
Console.WriteLine($"\"{book.Title}\" is on loan to another patron. The return due date is {loan.DueDate}.");
}

return ConsoleState.PatronDetails;
}


async Task<ConsoleState> LoanDetails()
{
Console.WriteLine($"Book title: {selectedLoanDetails.BookItem!.Book!.Title}");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
using NSubstitute;
using Library.ApplicationCore;
using Library.ApplicationCore.Entities;
using Library.Infrastructure.Data;
using Microsoft.Extensions.Configuration;
using Xunit;

namespace UnitTests.Infrastructure.JsonLoanRepositoryTests;

public class GetLoanTest
{
private readonly ILoanRepository _mockLoanRepository;
private readonly JsonLoanRepository _jsonLoanRepository;
private readonly IConfiguration _configuration;
private readonly JsonData _jsonData;

public GetLoanTest()
{
_mockLoanRepository = Substitute.For<ILoanRepository>();
_configuration = new ConfigurationBuilder().Build();
_jsonData = new JsonData(_configuration);
_jsonLoanRepository = new JsonLoanRepository(_jsonData);
}

[Fact(DisplayName = "JsonLoanRepository.GetLoan: Returns loan when loan ID is found")]
public async Task GetLoan_ReturnsLoanWhenLoanIdIsFound()
{
// Arrange
var loanId = 1; // Use a loan ID that exists in the Loans.json file
var expectedLoan = new Loan
{
Id = loanId,
BookItemId = 17,
PatronId = 22,
LoanDate = DateTime.Parse("2023-12-08T00:40:43.1808862"),
DueDate = DateTime.Parse("2023-12-22T00:40:43.1808862"),
ReturnDate = null
};

_mockLoanRepository.GetLoan(loanId).Returns(expectedLoan);

// Act
var actualLoan = await _jsonLoanRepository.GetLoan(loanId);

// Assert
Assert.NotNull(actualLoan);
Assert.Equal(expectedLoan.Id, actualLoan?.Id);
}


[Fact(DisplayName = "JsonLoanRepository.GetLoan: Returns null when loan ID is not found")]
public async Task GetLoan_ReturnsNullWhenLoanIdIsNotFound()
{
// Arrange
var loanId = 999; // Use a loan ID that does not exist in the Loans.json file
var expectedLoan = new Loan { Id = loanId, BookItemId = 101, PatronId = 202, LoanDate = DateTime.Now, DueDate = DateTime.Now.AddDays(14) };
_mockLoanRepository.GetLoan(loanId).Returns(expectedLoan);

// Act
var actualLoan = await _jsonLoanRepository.GetLoan(loanId);

// Assert
Assert.Null(actualLoan);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@

<ItemGroup>
<ProjectReference Include="..\..\src\Library.ApplicationCore\Library.ApplicationCore.csproj" />
<ProjectReference Include="..\..\src\Library.Infrastructure\Library.Infrastructure.csproj" />
</ItemGroup>

<ItemGroup>
<None Include="..\..\src\Library.Console\Json\**\*">
<Link>Json\%(RecursiveDir)%(FileName)%(Extension)</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -1,27 +1,53 @@
using System.ComponentModel;
using System.Reflection;
using System.Collections.Generic;

namespace Library.ApplicationCore.Enums;

public static class EnumHelper
{
private static readonly IReadOnlyDictionary<LoanExtensionStatus, string> LoanExtensionStatusDescriptions =
new Dictionary<LoanExtensionStatus, string>
{
[LoanExtensionStatus.Success] = "Book loan extension was successful.",
[LoanExtensionStatus.LoanNotFound] = "Loan not found.",
[LoanExtensionStatus.LoanExpired] = "Cannot extend book loan as it already has expired. Return the book instead.",
[LoanExtensionStatus.MembershipExpired] = "Cannot extend book loan due to expired patron's membership.",
[LoanExtensionStatus.LoanReturned] = "Cannot extend book loan as the book is already returned.",
[LoanExtensionStatus.Error] = "Cannot extend book loan due to an error."
};

private static readonly IReadOnlyDictionary<LoanReturnStatus, string> LoanReturnStatusDescriptions =
new Dictionary<LoanReturnStatus, string>
{
[LoanReturnStatus.Success] = "Book was successfully returned.",
[LoanReturnStatus.LoanNotFound] = "Loan not found.",
[LoanReturnStatus.AlreadyReturned] = "Cannot return book as the book is already returned.",
[LoanReturnStatus.Error] = "Cannot return book due to an error."
};

private static readonly IReadOnlyDictionary<MembershipRenewalStatus, string> MembershipRenewalStatusDescriptions =
new Dictionary<MembershipRenewalStatus, string>
{
[MembershipRenewalStatus.Success] = "Membership renewal was successful.",
[MembershipRenewalStatus.PatronNotFound] = "Patron not found.",
[MembershipRenewalStatus.TooEarlyToRenew] = "It is too early to renew the membership.",
[MembershipRenewalStatus.LoanNotReturned] = "Cannot renew membership due to an outstanding loan.",
[MembershipRenewalStatus.Error] = "Cannot renew membership due to an error."
};

public static string GetDescription(Enum value)
{
if (value == null)
return string.Empty;

FieldInfo fieldInfo = value.GetType().GetField(value.ToString())!;

DescriptionAttribute[] attributes =
(DescriptionAttribute[])fieldInfo.GetCustomAttributes(typeof(DescriptionAttribute), false);

if (attributes != null && attributes.Length > 0)
{
return attributes[0].Description;
}
else
return value switch
{
return value.ToString();
}
LoanExtensionStatus loanExtensionStatus when LoanExtensionStatusDescriptions.TryGetValue(loanExtensionStatus, out var extensionDescription)
=> extensionDescription,
LoanReturnStatus loanReturnStatus when LoanReturnStatusDescriptions.TryGetValue(loanReturnStatus, out var returnDescription)
=> returnDescription,
MembershipRenewalStatus membershipRenewalStatus when MembershipRenewalStatusDescriptions.TryGetValue(membershipRenewalStatus, out var membershipDescription)
=> membershipDescription,
_ => value.ToString()
};
}
}
Loading